diff --git a/.github/workflows/diff.yaml b/.github/workflows/diff.yaml
index 8b47b3bb3..a7faa7d22 100644
--- a/.github/workflows/diff.yaml
+++ b/.github/workflows/diff.yaml
@@ -99,7 +99,7 @@ jobs:
             echo ${file}
             if [[ ${file} == *.py ]]; then
               python3 -m black --check --diff ${file}
-              python3 -m isort --check-only --diff ${file}
+              python3 -m isort --check-only --profile "black" --diff ${file}
             fi
           done
 
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5abd746c9..26b7c8e05 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -14,6 +14,7 @@ repos:
     hooks:
       - id: isort
         name: isort (python)
+        args: ["--profile", "black"]
   - repo: https://github.com/pre-commit/mirrors-clang-format
     rev: v13.0.0
     hooks:
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8b84bb26a..81fc61836 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -21,6 +21,7 @@ add_subdirectory(auth)
 add_subdirectory(parser)
 add_subdirectory(expr)
 add_subdirectory(coordinator)
+add_subdirectory(functions)
 
 if (MG_ENTERPRISE)
   add_subdirectory(audit)
diff --git a/src/expr/CMakeLists.txt b/src/expr/CMakeLists.txt
index e529512b7..31cbfa493 100644
--- a/src/expr/CMakeLists.txt
+++ b/src/expr/CMakeLists.txt
@@ -17,4 +17,4 @@ target_include_directories(mg-expr PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
 target_include_directories(mg-expr PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/ast)
 target_include_directories(mg-expr PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/interpret)
 target_include_directories(mg-expr PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/semantic)
-target_link_libraries(mg-expr cppitertools Boost::headers mg-utils mg-parser)
+target_link_libraries(mg-expr cppitertools Boost::headers mg-utils mg-parser mg-functions)
diff --git a/src/expr/interpret/eval.hpp b/src/expr/interpret/eval.hpp
index a7d027ede..8fb300a83 100644
--- a/src/expr/interpret/eval.hpp
+++ b/src/expr/interpret/eval.hpp
@@ -24,6 +24,7 @@
 #include "expr/exceptions.hpp"
 #include "expr/interpret/frame.hpp"
 #include "expr/semantic/symbol_table.hpp"
+#include "functions/awesome_memgraph_functions.hpp"
 #include "utils/exceptions.hpp"
 
 namespace memgraph::expr {
@@ -427,8 +428,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
             typename TReturnType = std::enable_if_t<std::is_same_v<TTag, QueryEngineTag>, bool>>
   TReturnType HasLabelImpl(const VertexAccessor &vertex, const LabelIx &label_ix, QueryEngineTag /*tag*/) {
     auto label = typename VertexAccessor::Label{LabelId::FromUint(label_ix.ix)};
-    auto has_label = vertex.HasLabel(label);
-    return !has_label;
+    return vertex.HasLabel(label);
   }
 
   TypedValue Visit(LabelsTest &labels_test) override {
@@ -491,7 +491,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
   }
 
   TypedValue Visit(Function &function) override {
-    FunctionContext function_ctx{dba_, ctx_->memory, ctx_->timestamp, &ctx_->counters, view_};
+    functions::FunctionContext<DbAccessor> function_ctx{dba_, ctx_->memory, ctx_->timestamp, &ctx_->counters, view_};
     // Stack allocate evaluated arguments when there's a small number of them.
     if (function.arguments_.size() <= 8) {
       TypedValue arguments[8] = {TypedValue(ctx_->memory), TypedValue(ctx_->memory), TypedValue(ctx_->memory),
diff --git a/src/functions/CMakeLists.txt b/src/functions/CMakeLists.txt
new file mode 100644
index 000000000..3a3d430cd
--- /dev/null
+++ b/src/functions/CMakeLists.txt
@@ -0,0 +1 @@
+add_library(mg-functions INTERFACE)
diff --git a/src/functions/awesome_memgraph_functions.hpp b/src/functions/awesome_memgraph_functions.hpp
new file mode 100644
index 000000000..7e716d970
--- /dev/null
+++ b/src/functions/awesome_memgraph_functions.hpp
@@ -0,0 +1,1423 @@
+// Copyright 2022 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#include <functional>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+
+#include "storage/v3/result.hpp"
+#include "storage/v3/shard.hpp"
+#include "storage/v3/view.hpp"
+#include "utils/algorithm.hpp"
+#include "utils/cast.hpp"
+#include "utils/concepts.hpp"
+#include "utils/memory.hpp"
+#include "utils/pmr/string.hpp"
+#include "utils/string.hpp"
+#include "utils/temporal.hpp"
+
+namespace memgraph::functions {
+
+class FunctionRuntimeException : public utils::BasicException {
+  using utils::BasicException::BasicException;
+};
+
+template <typename TAccessor>
+struct FunctionContext {
+  TAccessor *db_accessor;
+  utils::MemoryResource *memory;
+  int64_t timestamp;
+  std::unordered_map<std::string, int64_t> *counters;
+  storage::v3::View view;
+};
+
+// Tags for the NameToFunction() function template
+struct StorageEngineTag {};
+struct QueryEngineTag {};
+
+/// Return the function implementation with the given name.
+///
+/// Note, returned function signature uses C-style access to an array to allow
+/// having an array stored anywhere the caller likes, as long as it is
+/// contiguous in memory. Since most functions don't take many arguments, it's
+/// convenient to have them stored in the calling stack frame.
+template <typename TypedValueT, typename FunctionContextT, typename Tag, typename Conv>
+std::function<TypedValueT(const TypedValueT *arguments, int64_t num_arguments, const FunctionContextT &context)>
+NameToFunction(const std::string &function_name);
+
+inline constexpr char kStartsWith[] = "STARTSWITH";
+inline constexpr char kEndsWith[] = "ENDSWITH";
+inline constexpr char kContains[] = "CONTAINS";
+inline constexpr char kId[] = "ID";
+
+}  // namespace memgraph::functions
+
+namespace memgraph::functions::impl {
+
+////////////////////////////////////////////////////////////////////////////////
+// eDSL using template magic for describing a type of an awesome memgraph
+// function and checking if the passed in arguments match the description.
+//
+// To use the type checking eDSL, you should put a `FType` invocation in the
+// body of your awesome Memgraph function. `FType` takes type arguments as the
+// description of the function type signature. Each runtime argument will be
+// checked in order corresponding to given compile time type arguments. These
+// type arguments can come in two forms:
+//
+//   * final, primitive type descriptor and
+//   * combinator type descriptor.
+//
+// The primitive type descriptors are defined as empty structs, they are right
+// below this documentation.
+//
+// Combinator type descriptors are defined as structs taking additional type
+// parameters, you can find these further below in the implementation. Of
+// primary interest are `Or` and `Optional` type combinators.
+//
+// With `Or` you can describe that an argument can be any of the types listed in
+// `Or`. For example, `Or<Null, Bool, Integer>` allows an argument to be either
+// `Null` or a boolean or an integer.
+//
+// The `Optional` combinator is used to define optional arguments to a function.
+// These must come as the last positional arguments. Naturally, you can use `Or`
+// inside `Optional`. So for example, `Optional<Or<Null, Bool>, Integer>`
+// describes that a function takes 2 optional arguments. The 1st one must be
+// either a `Null` or a boolean, while the 2nd one must be an integer. The type
+// signature check will succeed in the following cases.
+//
+//   * No optional arguments were supplied.
+//   * One argument was supplied and it passes `Or<Null, Bool>` check.
+//   * Two arguments were supplied, the 1st one passes `Or<Null, Bool>` check
+//     and the 2nd one passes `Integer` check.
+//
+// Runtime arguments to `FType` are: function name, pointer to arguments and the
+// number of received arguments.
+//
+// Full example.
+//
+//     FType<Or<Null, String>, NonNegativeInteger,
+//           Optional<NonNegativeInteger>>("substring", args, nargs);
+//
+// The above will check that `substring` function received the 2 required
+// arguments. Optionally, the function may take a 3rd argument. The 1st argument
+// must be either a `Null` or a character string. The 2nd argument is required
+// to be a non-negative integer. If the 3rd argument was supplied, it will also
+// be checked that it is a non-negative integer. If any of these checks fail,
+// `FType` will throw a `FunctionRuntimeException` with an appropriate error
+// message.
+////////////////////////////////////////////////////////////////////////////////
+
+struct Null {};
+struct Bool {};
+struct Integer {};
+struct PositiveInteger {};
+struct NonZeroInteger {};
+struct NonNegativeInteger {};
+struct Double {};
+struct Number {};
+struct List {};
+struct String {};
+struct Map {};
+struct Edge {};
+struct Vertex {};
+struct Path {};
+struct Date {};
+struct LocalTime {};
+struct LocalDateTime {};
+struct Duration {};
+
+template <typename ArgType, typename TypedValueT>
+bool ArgIsType(const TypedValueT &arg) {
+  if constexpr (std::is_same_v<ArgType, Null>) {
+    return arg.IsNull();
+  } else if constexpr (std::is_same_v<ArgType, Bool>) {
+    return arg.IsBool();
+  } else if constexpr (std::is_same_v<ArgType, Integer>) {
+    return arg.IsInt();
+  } else if constexpr (std::is_same_v<ArgType, PositiveInteger>) {
+    return arg.IsInt() && arg.ValueInt() > 0;
+  } else if constexpr (std::is_same_v<ArgType, NonZeroInteger>) {
+    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>) {
+    return arg.IsList();
+  } else if constexpr (std::is_same_v<ArgType, String>) {
+    return arg.IsString();
+  } else if constexpr (std::is_same_v<ArgType, Map>) {
+    return arg.IsMap();
+  } else if constexpr (std::is_same_v<ArgType, Vertex>) {
+    return arg.IsVertex();
+  } else if constexpr (std::is_same_v<ArgType, Edge>) {
+    return arg.IsEdge();
+  } else if constexpr (std::is_same_v<ArgType, Path>) {
+    return arg.IsPath();
+  } else if constexpr (std::is_same_v<ArgType, Date>) {
+    return arg.IsDate();
+  } else if constexpr (std::is_same_v<ArgType, LocalTime>) {
+    return arg.IsLocalTime();
+  } else if constexpr (std::is_same_v<ArgType, LocalDateTime>) {
+    return arg.IsLocalDateTime();
+  } else if constexpr (std::is_same_v<ArgType, Duration>) {
+    return arg.IsDuration();
+  } else if constexpr (std::is_same_v<ArgType, void>) {
+    return true;
+  } else {
+    static_assert(std::is_same_v<ArgType, Null>, "Unknown ArgType");
+  }
+  return false;
+}
+
+template <typename 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>) {
+    return "boolean";
+  } else if constexpr (std::is_same_v<ArgType, Integer>) {
+    return "integer";
+  } else if constexpr (std::is_same_v<ArgType, PositiveInteger>) {
+    return "positive integer";
+  } else if constexpr (std::is_same_v<ArgType, NonZeroInteger>) {
+    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>) {
+    return "list";
+  } else if constexpr (std::is_same_v<ArgType, String>) {
+    return "string";
+  } else if constexpr (std::is_same_v<ArgType, Map>) {
+    return "map";
+  } else if constexpr (std::is_same_v<ArgType, Vertex>) {
+    return "node";
+  } else if constexpr (std::is_same_v<ArgType, Edge>) {
+    return "relationship";
+  } else if constexpr (std::is_same_v<ArgType, Path>) {
+    return "path";
+  } else if constexpr (std::is_same_v<ArgType, void>) {
+    return "void";
+  } else if constexpr (std::is_same_v<ArgType, Date>) {
+    return "Date";
+  } else if constexpr (std::is_same_v<ArgType, LocalTime>) {
+    return "LocalTime";
+  } else if constexpr (std::is_same_v<ArgType, LocalDateTime>) {
+    return "LocalDateTime";
+  } else if constexpr (std::is_same_v<ArgType, Duration>) {
+    return "Duration";
+  } else {
+    static_assert(std::is_same_v<ArgType, Null>, "Unknown ArgType");
+  }
+  return "<unknown-type>";
+}
+
+template <typename... ArgType>
+struct Or;
+
+template <typename ArgType>
+struct Or<ArgType> {
+  template <typename TypedValueT>
+  static bool Check(const TypedValueT &arg) {
+    return ArgIsType<ArgType>(arg);
+  }
+
+  static std::string TypeNames() { return ArgTypeName<ArgType>(); }
+};
+
+template <typename ArgType, typename... ArgTypes>
+struct Or<ArgType, ArgTypes...> {
+  template <typename TypedValueT>
+  static bool Check(const TypedValueT &arg) {
+    if (ArgIsType<ArgType>(arg)) return true;
+    return Or<ArgTypes...>::Check(arg);
+  }
+
+  static std::string TypeNames() {
+    if constexpr (sizeof...(ArgTypes) > 1) {
+      return fmt::format("'{}', {}", ArgTypeName<ArgType>(), Or<ArgTypes...>::TypeNames());
+    } else {
+      return fmt::format("'{}' or '{}'", ArgTypeName<ArgType>(), Or<ArgTypes...>::TypeNames());
+    }
+  }
+};
+
+template <class T>
+struct IsOrType {
+  static constexpr bool value = false;
+};
+
+template <class... ArgTypes>
+struct IsOrType<Or<ArgTypes...>> {
+  static constexpr bool value = true;
+};
+
+template <typename... ArgTypes>
+struct Optional;
+
+template <typename ArgType>
+struct Optional<ArgType> {
+  static constexpr size_t size = 1;
+
+  template <typename TypedValueT>
+  static void Check(const char *name, const TypedValueT *args, int64_t nargs, int64_t pos) {
+    if (nargs == 0) return;
+    const TypedValueT &arg = args[0];
+    if constexpr (IsOrType<ArgType>::value) {
+      if (!ArgType::Check(arg)) {
+        throw FunctionRuntimeException("Optional '{}' argument at position {} must be either {}.", name, pos,
+                                       ArgType::TypeNames());
+      }
+    } else {
+      if (!ArgIsType<ArgType>(arg))
+        throw FunctionRuntimeException("Optional '{}' argument at position {} must be '{}'.", name, pos,
+                                       ArgTypeName<ArgType>());
+    }
+  }
+};
+
+template <class ArgType, class... ArgTypes>
+struct Optional<ArgType, ArgTypes...> {
+  static constexpr size_t size = 1 + sizeof...(ArgTypes);
+
+  template <typename TypedValueT>
+  static void Check(const char *name, const TypedValueT *args, int64_t nargs, int64_t pos) {
+    if (nargs == 0) return;
+    Optional<ArgType>::Check(name, args, nargs, pos);
+    Optional<ArgTypes...>::Check(name, args + 1, nargs - 1, pos + 1);
+  }
+};
+
+template <class T>
+struct IsOptional {
+  static constexpr bool value = false;
+};
+
+template <class... ArgTypes>
+struct IsOptional<Optional<ArgTypes...>> {
+  static constexpr bool value = true;
+};
+
+template <class ArgType, class... ArgTypes>
+constexpr size_t FTypeRequiredArgs() {
+  if constexpr (IsOptional<ArgType>::value) {
+    static_assert(sizeof...(ArgTypes) == 0, "Optional arguments must be last!");
+    return 0;
+  } else if constexpr (sizeof...(ArgTypes) == 0) {
+    return 1;
+  } else {
+    return 1U + FTypeRequiredArgs<ArgTypes...>();
+  }
+}
+
+template <class ArgType, class... ArgTypes>
+constexpr size_t FTypeOptionalArgs() {
+  if constexpr (IsOptional<ArgType>::value) {
+    static_assert(sizeof...(ArgTypes) == 0, "Optional arguments must be last!");
+    return ArgType::size;
+  } else if constexpr (sizeof...(ArgTypes) == 0) {
+    return 0;
+  } else {
+    return FTypeOptionalArgs<ArgTypes...>();
+  }
+}
+
+template <typename TypedValueT, typename ArgType, typename... ArgTypes>
+void FType(const char *name, const TypedValueT *args, int64_t nargs, int64_t pos = 1) {
+  if constexpr (std::is_same_v<ArgType, void>) {
+    if (nargs != 0) {
+      throw FunctionRuntimeException("'{}' requires no arguments.", name);
+    }
+    return;
+  }
+  static constexpr int64_t required_args = FTypeRequiredArgs<ArgType, ArgTypes...>();
+  static constexpr int64_t optional_args = FTypeOptionalArgs<ArgType, ArgTypes...>();
+  static constexpr int64_t total_args = required_args + optional_args;
+  if constexpr (optional_args > 0) {
+    if (nargs < required_args || nargs > total_args) {
+      throw FunctionRuntimeException("'{}' requires between {} and {} arguments.", name, required_args, total_args);
+    }
+  } else {
+    if (nargs != required_args) {
+      throw FunctionRuntimeException("'{}' requires exactly {} {}.", name, required_args,
+                                     required_args == 1 ? "argument" : "arguments");
+    }
+  }
+  const TypedValueT &arg = args[0];
+  if constexpr (IsOrType<ArgType>::value) {
+    if (!ArgType::Check(arg)) {
+      throw FunctionRuntimeException("'{}' argument at position {} must be either {}.", name, pos,
+                                     ArgType::TypeNames());
+    }
+  } else if constexpr (IsOptional<ArgType>::value) {
+    static_assert(sizeof...(ArgTypes) == 0, "Optional arguments must be last!");
+    ArgType::Check(name, args, nargs, pos);
+  } else {
+    if (!ArgIsType<ArgType>(arg)) {
+      throw FunctionRuntimeException("'{}' argument at position {} must be '{}'", name, pos, ArgTypeName<ArgType>());
+    }
+  }
+  if constexpr (sizeof...(ArgTypes) > 0) {
+    FType<TypedValueT, ArgTypes...>(name, args + 1, nargs - 1, pos + 1);
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// END function type description eDSL
+////////////////////////////////////////////////////////////////////////////////
+
+// Predicate functions.
+// Neo4j has all, any, exists, none, single
+// Those functions are a little bit different since they take a filterExpression
+// as an argument.
+// There is all, any, none and single productions in opencypher grammar, but it
+// will be trivial to also add exists.
+// TODO: Implement this.
+
+// Scalar functions.
+// We don't have a way to implement id function since we don't store any. If it
+// is really neccessary we could probably map vlist* to id.
+// TODO: Implement length (it works on a path, but we didn't define path
+// structure yet).
+// TODO: Implement size(pattern), for example size((a)-[:X]-()) should return
+// number of results of this pattern. I don't think we will ever do this.
+// TODO: Implement rest of the list functions.
+// TODO: Implement degrees, haversin, radians
+// TODO: Implement spatial functions
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT EndNode(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Edge>>("endNode", args, nargs);
+  if (args[0].IsNull()) {
+    return TypedValueT(ctx.memory);
+  }
+  return TypedValueT(args[0].ValueEdge().To(), ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Head(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, List>>("head", args, nargs);
+  if (args[0].IsNull()) return TypedValueT(ctx.memory);
+  const auto &list = args[0].ValueList();
+  if (list.empty()) return TypedValueT(ctx.memory);
+  return TypedValueT(list[0], ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Last(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, List>>("last", args, nargs);
+  if (args[0].IsNull()) return TypedValueT(ctx.memory);
+  const auto &list = args[0].ValueList();
+  if (list.empty()) return TypedValueT(ctx.memory);
+  return TypedValueT(list.back(), ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT, typename Tag, typename Conv>
+TypedValueT Properties(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Vertex, Edge>>("properties", args, nargs);
+  auto *dba = ctx.db_accessor;
+  auto get_properties = [&](const auto &record_accessor) {
+    typename TypedValueT::TMap properties(ctx.memory);
+    Conv conv;
+    if constexpr (std::is_same_v<Tag, StorageEngineTag>) {
+      auto maybe_props = record_accessor.Properties(ctx.view);
+      if (maybe_props.HasError()) {
+        switch (maybe_props.GetError().code) {
+          case common::ErrorCode::DELETED_OBJECT:
+            throw functions::FunctionRuntimeException("Trying to get properties from a deleted object.");
+          case common::ErrorCode::NONEXISTENT_OBJECT:
+            throw functions::FunctionRuntimeException("Trying to get properties from an object that doesn't exist.");
+          case common::ErrorCode::SERIALIZATION_ERROR:
+          case common::ErrorCode::VERTEX_HAS_EDGES:
+          case common::ErrorCode::PROPERTIES_DISABLED:
+          case common::ErrorCode::VERTEX_ALREADY_INSERTED:
+          case common::ErrorCode::SCHEMA_NO_SCHEMA_DEFINED_FOR_LABEL:
+          case common::ErrorCode::SCHEMA_VERTEX_PROPERTY_WRONG_TYPE:
+          case common::ErrorCode::SCHEMA_VERTEX_UPDATE_PRIMARY_KEY:
+          case common::ErrorCode::SCHEMA_VERTEX_UPDATE_PRIMARY_LABEL:
+          case common::ErrorCode::SCHEMA_VERTEX_SECONDARY_LABEL_IS_PRIMARY:
+          case common::ErrorCode::SCHEMA_VERTEX_PRIMARY_PROPERTIES_UNDEFINED:
+          case common::ErrorCode::OBJECT_NOT_FOUND:
+            throw functions::FunctionRuntimeException("Unexpected error when getting properties.");
+        }
+      }
+      for (const auto &property : *maybe_props) {
+        properties.emplace(dba->PropertyToName(property.first), conv(property.second));
+      }
+    } else {
+      for (const auto &property : record_accessor.Properties()) {
+        properties.emplace(utils::pmr::string(dba->PropertyToName(property.first), ctx.memory),
+                           conv(property.second, dba));
+      }
+    }
+    return TypedValueT(std::move(properties));
+  };
+
+  const auto &value = args[0];
+  if (value.IsNull()) {
+    return TypedValueT(ctx.memory);
+  }
+  if (value.IsVertex()) {
+    return get_properties(value.ValueVertex());
+  }
+  return get_properties(value.ValueEdge());
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Size(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, List, String, Map, Path>>("size", args, nargs);
+  const auto &value = args[0];
+  if (value.IsNull()) {
+    return TypedValueT(ctx.memory);
+  }
+  if (value.IsList()) {
+    return TypedValueT(static_cast<int64_t>(value.ValueList().size()), ctx.memory);
+  }
+  if (value.IsString()) {
+    return TypedValueT(static_cast<int64_t>(value.ValueString().size()), ctx.memory);
+  }
+  if (value.IsMap()) {
+    // neo4j doesn't implement size for map, but I don't see a good reason not
+    // to do it.
+    return TypedValueT(static_cast<int64_t>(value.ValueMap().size()), ctx.memory);
+  }
+  return TypedValueT(static_cast<int64_t>(value.ValuePath().edges().size()), ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT, typename Conv>
+TypedValueT StartNode(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Edge>>("startNode", args, nargs);
+  if (args[0].IsNull()) {
+    return TypedValueT(ctx.memory);
+  }
+  return TypedValueT(args[0].ValueEdge().From(), ctx.memory);
+}
+
+// This is needed because clang-tidy fails to identify the use of this function in the if-constexpr branch
+// NOLINTNEXTLINE(clang-diagnostic-unused-function)
+inline size_t UnwrapDegreeResult(storage::v3::ShardResult<size_t> maybe_degree) {
+  if (maybe_degree.HasError()) {
+    switch (maybe_degree.GetError().code) {
+      case common::ErrorCode::DELETED_OBJECT:
+        throw functions::FunctionRuntimeException("Trying to get degree of a deleted node.");
+      case common::ErrorCode::NONEXISTENT_OBJECT:
+        throw functions::FunctionRuntimeException("Trying to get degree of a node that doesn't exist.");
+      case common::ErrorCode::SERIALIZATION_ERROR:
+      case common::ErrorCode::VERTEX_HAS_EDGES:
+      case common::ErrorCode::PROPERTIES_DISABLED:
+      case common::ErrorCode::VERTEX_ALREADY_INSERTED:
+      case common::ErrorCode::SCHEMA_NO_SCHEMA_DEFINED_FOR_LABEL:
+      case common::ErrorCode::SCHEMA_VERTEX_PROPERTY_WRONG_TYPE:
+      case common::ErrorCode::SCHEMA_VERTEX_UPDATE_PRIMARY_KEY:
+      case common::ErrorCode::SCHEMA_VERTEX_UPDATE_PRIMARY_LABEL:
+      case common::ErrorCode::SCHEMA_VERTEX_SECONDARY_LABEL_IS_PRIMARY:
+      case common::ErrorCode::SCHEMA_VERTEX_PRIMARY_PROPERTIES_UNDEFINED:
+      case common::ErrorCode::OBJECT_NOT_FOUND:
+        throw functions::FunctionRuntimeException("Unexpected error when getting node degree.");
+    }
+  }
+  return *maybe_degree;
+}
+
+template <typename TypedValueT, typename FunctionContextT, typename Tag>
+TypedValueT Degree(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Vertex>>("degree", args, nargs);
+  if (args[0].IsNull()) return TypedValueT(ctx.memory);
+  const auto &vertex = args[0].ValueVertex();
+  size_t out_degree = 0;
+  size_t in_degree = 0;
+  if constexpr (std::same_as<Tag, StorageEngineTag>) {
+    out_degree = UnwrapDegreeResult(vertex.OutDegree(ctx.view));
+    in_degree = UnwrapDegreeResult(vertex.InDegree(ctx.view));
+  } else {
+    out_degree = vertex.OutDegree();
+    in_degree = vertex.InDegree();
+  }
+  return TypedValueT(static_cast<int64_t>(out_degree + in_degree), ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT, typename Tag>
+TypedValueT InDegree(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Vertex>>("inDegree", args, nargs);
+  if (args[0].IsNull()) return TypedValueT(ctx.memory);
+  const auto &vertex = args[0].ValueVertex();
+  size_t in_degree = 0;
+  if constexpr (std::same_as<Tag, StorageEngineTag>) {
+    in_degree = UnwrapDegreeResult(vertex.InDegree(ctx.view));
+  } else {
+    in_degree = vertex.InDegree();
+  }
+  return TypedValueT(static_cast<int64_t>(in_degree), ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT, typename Tag>
+TypedValueT OutDegree(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Vertex>>("outDegree", args, nargs);
+  if (args[0].IsNull()) return TypedValueT(ctx.memory);
+  const auto &vertex = args[0].ValueVertex();
+  size_t out_degree = 0;
+  if constexpr (std::same_as<Tag, StorageEngineTag>) {
+    out_degree = UnwrapDegreeResult(vertex.OutDegree(ctx.view));
+  } else {
+    out_degree = vertex.OutDegree();
+  }
+  return TypedValueT(static_cast<int64_t>(out_degree), ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT ToBoolean(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Bool, Integer, String>>("toBoolean", args, nargs);
+  const auto &value = args[0];
+  if (value.IsNull()) {
+    return TypedValueT(ctx.memory);
+  }
+  if (value.IsBool()) {
+    return TypedValueT(value.ValueBool(), ctx.memory);
+  }
+  if (value.IsInt()) {
+    return TypedValueT(value.ValueInt() != 0L, ctx.memory);
+  }
+  auto s = utils::ToUpperCase(utils::Trim(value.ValueString()));
+  if (s == "TRUE") return TypedValueT(true, ctx.memory);
+  if (s == "FALSE") return TypedValueT(false, ctx.memory);
+  // I think this is just stupid and that exception should be thrown, but
+  // neo4j does it this way...
+  return TypedValueT(ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT ToFloat(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Number, String>>("toFloat", args, nargs);
+  const auto &value = args[0];
+  if (value.IsNull()) {
+    return TypedValueT(ctx.memory);
+  }
+  if (value.IsInt()) {
+    return TypedValueT(static_cast<double>(value.ValueInt()), ctx.memory);
+  }
+  if (value.IsDouble()) {
+    return TypedValueT(value, ctx.memory);
+  }
+  try {
+    return TypedValueT(utils::ParseDouble(utils::Trim(value.ValueString())), ctx.memory);
+  } catch (const utils::BasicException &) {
+    return TypedValueT(ctx.memory);
+  }
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT ToInteger(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Bool, Number, String>>("toInteger", args, nargs);
+  const auto &value = args[0];
+  if (value.IsNull()) {
+    return TypedValueT(ctx.memory);
+  }
+  if (value.IsBool()) {
+    return TypedValueT(value.ValueBool() ? 1L : 0L, ctx.memory);
+  }
+  if (value.IsInt()) {
+    return TypedValueT(value, ctx.memory);
+  }
+  if (value.IsDouble()) {
+    return TypedValueT(static_cast<int64_t>(value.ValueDouble()), ctx.memory);
+  }
+  try {
+    // Yup, this is correct. String is valid if it has floating point
+    // number, then it is parsed and converted to int.
+    return TypedValueT(static_cast<int64_t>(utils::ParseDouble(utils::Trim(value.ValueString()))), ctx.memory);
+  } catch (const utils::BasicException &) {
+    return TypedValueT(ctx.memory);
+  }
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Type(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Edge>>("type", args, nargs);
+  auto *dba = ctx.db_accessor;
+  if (args[0].IsNull()) return TypedValueT(ctx.memory);
+  return TypedValueT(dba->EdgeTypeToName(args[0].ValueEdge().EdgeType()), ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT ValueType(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, 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 TypedValueT::Type::Null:
+      return TypedValueT("NULL", ctx.memory);
+    case TypedValueT::Type::Bool:
+      return TypedValueT("BOOLEAN", ctx.memory);
+    case TypedValueT::Type::Int:
+      return TypedValueT("INTEGER", ctx.memory);
+    case TypedValueT::Type::Double:
+      return TypedValueT("FLOAT", ctx.memory);
+    case TypedValueT::Type::String:
+      return TypedValueT("STRING", ctx.memory);
+    case TypedValueT::Type::List:
+      return TypedValueT("LIST", ctx.memory);
+    case TypedValueT::Type::Map:
+      return TypedValueT("MAP", ctx.memory);
+    case TypedValueT::Type::Vertex:
+      return TypedValueT("NODE", ctx.memory);
+    case TypedValueT::Type::Edge:
+      return TypedValueT("RELATIONSHIP", ctx.memory);
+    case TypedValueT::Type::Path:
+      return TypedValueT("PATH", ctx.memory);
+    case TypedValueT::Type::Date:
+      return TypedValueT("DATE", ctx.memory);
+    case TypedValueT::Type::LocalTime:
+      return TypedValueT("LOCAL_TIME", ctx.memory);
+    case TypedValueT::Type::LocalDateTime:
+      return TypedValueT("LOCAL_DATE_TIME", ctx.memory);
+    case TypedValueT::Type::Duration:
+      return TypedValueT("DURATION", ctx.memory);
+  }
+}
+
+template <typename TypedValueT, typename FunctionContextT, typename Tag>
+TypedValueT Labels(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Vertex>>("labels", args, nargs);
+  auto *dba = ctx.db_accessor;
+  if (args[0].IsNull()) return TypedValueT(ctx.memory);
+  typename TypedValueT::TVector labels(ctx.memory);
+  if constexpr (std::is_same_v<Tag, StorageEngineTag>) {
+    auto maybe_labels = args[0].ValueVertex().Labels(ctx.view);
+    if (maybe_labels.HasError()) {
+      switch (maybe_labels.GetError().code) {
+        case common::ErrorCode::DELETED_OBJECT:
+          throw functions::FunctionRuntimeException("Trying to get labels from a deleted node.");
+        case common::ErrorCode::NONEXISTENT_OBJECT:
+          throw functions::FunctionRuntimeException("Trying to get labels from a node that doesn't exist.");
+        case common::ErrorCode::SERIALIZATION_ERROR:
+        case common::ErrorCode::VERTEX_HAS_EDGES:
+        case common::ErrorCode::PROPERTIES_DISABLED:
+        case common::ErrorCode::VERTEX_ALREADY_INSERTED:
+        case common::ErrorCode::SCHEMA_NO_SCHEMA_DEFINED_FOR_LABEL:
+        case common::ErrorCode::SCHEMA_VERTEX_PROPERTY_WRONG_TYPE:
+        case common::ErrorCode::SCHEMA_VERTEX_UPDATE_PRIMARY_KEY:
+        case common::ErrorCode::SCHEMA_VERTEX_UPDATE_PRIMARY_LABEL:
+        case common::ErrorCode::SCHEMA_VERTEX_SECONDARY_LABEL_IS_PRIMARY:
+        case common::ErrorCode::SCHEMA_VERTEX_PRIMARY_PROPERTIES_UNDEFINED:
+        case common::ErrorCode::OBJECT_NOT_FOUND:
+          throw functions::FunctionRuntimeException("Unexpected error when getting labels.");
+      }
+    }
+    for (const auto &label : *maybe_labels) {
+      labels.emplace_back(dba->LabelToName(label));
+    }
+  } else {
+    auto vertex = args[0].ValueVertex();
+    for (const auto &label : vertex.Labels()) {
+      labels.emplace_back(dba->LabelToName(label.id));
+    }
+  }
+  return TypedValueT(std::move(labels));
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Nodes(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Path>>("nodes", args, nargs);
+  if (args[0].IsNull()) return TypedValueT(ctx.memory);
+  const auto &vertices = args[0].ValuePath().vertices();
+  typename TypedValueT::TVector values(ctx.memory);
+  values.reserve(vertices.size());
+  for (const auto &v : vertices) values.emplace_back(v);
+  return TypedValueT(std::move(values));
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Relationships(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Path>>("relationships", args, nargs);
+  if (args[0].IsNull()) return TypedValueT(ctx.memory);
+  const auto &edges = args[0].ValuePath().edges();
+  typename TypedValueT::TVector values(ctx.memory);
+  values.reserve(edges.size());
+  for (const auto &e : edges) values.emplace_back(e);
+  return TypedValueT(std::move(values));
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Range(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Integer>, Or<Null, Integer>, Optional<Or<Null, NonZeroInteger>>>("range", args, nargs);
+  for (int64_t i = 0; i < nargs; ++i)
+    if (args[i].IsNull()) return TypedValueT(ctx.memory);
+  auto lbound = args[0].ValueInt();
+  auto rbound = args[1].ValueInt();
+  int64_t step = nargs == 3 ? args[2].ValueInt() : 1;
+  typename TypedValueT::TVector list(ctx.memory);
+  if (lbound <= rbound && step > 0) {
+    for (auto i = lbound; i <= rbound; i += step) {
+      list.emplace_back(i);
+    }
+  } else if (lbound >= rbound && step < 0) {
+    for (auto i = lbound; i >= rbound; i += step) {
+      list.emplace_back(i);
+    }
+  }
+  return TypedValueT(std::move(list));
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Tail(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, List>>("tail", args, nargs);
+  if (args[0].IsNull()) return TypedValueT(ctx.memory);
+  typename TypedValueT::TVector list(args[0].ValueList(), ctx.memory);
+  if (list.empty()) return TypedValueT(std::move(list));
+  list.erase(list.begin());
+  return TypedValueT(std::move(list));
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT UniformSample(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, List>, Or<Null, NonNegativeInteger>>("uniformSample", args, nargs);
+  static thread_local std::mt19937 pseudo_rand_gen_{std::random_device{}()};
+  if (args[0].IsNull() || args[1].IsNull()) return TypedValueT(ctx.memory);
+  const auto &population = args[0].ValueList();
+  auto population_size = population.size();
+  if (population_size == 0) return TypedValueT(ctx.memory);
+  auto desired_length = args[1].ValueInt();
+  std::uniform_int_distribution<uint64_t> rand_dist{0, population_size - 1};
+  typename TypedValueT::TVector sampled(ctx.memory);
+  sampled.reserve(desired_length);
+  for (int64_t i = 0; i < desired_length; ++i) {
+    sampled.emplace_back(population[rand_dist(pseudo_rand_gen_)]);
+  }
+  return TypedValueT(std::move(sampled));
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Abs(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Number>>("abs", args, nargs);
+  const auto &value = args[0];
+  if (value.IsNull()) {
+    return TypedValueT(ctx.memory);
+  }
+  if (value.IsInt()) {
+    return TypedValueT(std::abs(value.ValueInt()), ctx.memory);
+  }
+  return TypedValueT(std::abs(value.ValueDouble()), ctx.memory);
+}
+
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
+#define WRAP_CMATH_FLOAT_FUNCTION(name, lowercased_name)                                  \
+  template <typename TypedValueT, typename FunctionContextT>                              \
+  TypedValueT name(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) { \
+    FType<TypedValueT, Or<Null, Number>>(#lowercased_name, args, nargs);                  \
+    const auto &value = args[0];                                                          \
+    if (value.IsNull()) {                                                                 \
+      return TypedValueT(ctx.memory);                                                     \
+    }                                                                                     \
+    if (value.IsInt()) {                                                                  \
+      return TypedValueT(lowercased_name(value.ValueInt()), ctx.memory);                  \
+    }                                                                                     \
+    return TypedValueT(lowercased_name(value.ValueDouble()), ctx.memory);                 \
+  }
+
+WRAP_CMATH_FLOAT_FUNCTION(Ceil, ceil)
+WRAP_CMATH_FLOAT_FUNCTION(Floor, floor)
+// We are not completely compatible with neoj4 in this function because,
+// neo4j rounds -0.5, -1.5, -2.5... to 0, -1, -2...
+WRAP_CMATH_FLOAT_FUNCTION(Round, round)
+WRAP_CMATH_FLOAT_FUNCTION(Exp, exp)
+WRAP_CMATH_FLOAT_FUNCTION(Log, log)
+WRAP_CMATH_FLOAT_FUNCTION(Log10, log10)
+WRAP_CMATH_FLOAT_FUNCTION(Sqrt, sqrt)
+WRAP_CMATH_FLOAT_FUNCTION(Acos, acos)
+WRAP_CMATH_FLOAT_FUNCTION(Asin, asin)
+WRAP_CMATH_FLOAT_FUNCTION(Atan, atan)
+WRAP_CMATH_FLOAT_FUNCTION(Cos, cos)
+WRAP_CMATH_FLOAT_FUNCTION(Sin, sin)
+WRAP_CMATH_FLOAT_FUNCTION(Tan, tan)
+
+#undef WRAP_CMATH_FLOAT_FUNCTION
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Atan2(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Number>, Or<Null, Number>>("atan2", args, nargs);
+  if (args[0].IsNull() || args[1].IsNull()) return TypedValueT(ctx.memory);
+  auto to_double = [](const TypedValueT &t) -> double {
+    if (t.IsInt()) {
+      return t.ValueInt();
+    }
+    return t.ValueDouble();
+  };
+  double y = to_double(args[0]);
+  double x = to_double(args[1]);
+  return TypedValueT(atan2(y, x), ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Sign(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Number>>("sign", args, nargs);
+  auto sign = [&](auto x) { return TypedValueT((0 < x) - (x < 0), ctx.memory); };
+  const auto &value = args[0];
+  if (value.IsNull()) {
+    return TypedValueT(ctx.memory);
+  }
+  if (value.IsInt()) {
+    return sign(value.ValueInt());
+  }
+  return sign(value.ValueDouble());
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT E(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, void>("e", args, nargs);
+  return TypedValueT(M_E, ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Pi(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, void>("pi", args, nargs);
+  return TypedValueT(M_PI, ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Rand(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, void>("rand", args, nargs);
+  static thread_local std::mt19937 pseudo_rand_gen_{std::random_device{}()};
+  static thread_local std::uniform_real_distribution<> rand_dist_{0, 1};
+  return TypedValueT(rand_dist_(pseudo_rand_gen_), ctx.memory);
+}
+
+template <class TPredicate, typename TypedValueT, typename FunctionContextT>
+TypedValueT StringMatchOperator(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, String>, Or<Null, String>>(TPredicate::name, args, nargs);
+  if (args[0].IsNull() || args[1].IsNull()) return TypedValueT(ctx.memory);
+  const auto &s1 = args[0].ValueString();
+  const auto &s2 = args[1].ValueString();
+  return TypedValueT(TPredicate{}(s1, s2), ctx.memory);
+}
+
+// Check if s1 starts with s2.
+template <typename TypedValueT>
+struct StartsWithPredicate {
+  static constexpr const char *name = "startsWith";
+  bool operator()(const typename TypedValueT::TString &s1, const typename TypedValueT::TString &s2) const {
+    if (s1.size() < s2.size()) return false;
+    return std::equal(s2.begin(), s2.end(), s1.begin());
+  }
+};
+
+template <typename TypedValueT, typename FunctionContextT>
+inline const auto StartsWith = StringMatchOperator<StartsWithPredicate<TypedValueT>, TypedValueT, FunctionContextT>;
+
+// Check if s1 ends with s2.
+template <typename TypedValueT>
+struct EndsWithPredicate {
+  static constexpr const char *name = "endsWith";
+  bool operator()(const typename TypedValueT::TString &s1, const typename TypedValueT::TString &s2) const {
+    if (s1.size() < s2.size()) return false;
+    return std::equal(s2.rbegin(), s2.rend(), s1.rbegin());
+  }
+};
+
+template <typename TypedValueT, typename FunctionContextT>
+inline const auto EndsWith = StringMatchOperator<EndsWithPredicate<TypedValueT>, TypedValueT, FunctionContextT>;
+
+// Check if s1 contains s2.
+template <typename TypedValueT>
+struct ContainsPredicate {
+  static constexpr const char *name = "contains";
+  bool operator()(const typename TypedValueT::TString &s1, const typename TypedValueT::TString &s2) const {
+    if (s1.size() < s2.size()) return false;
+    return s1.find(s2) != std::string::npos;
+  }
+};
+
+template <typename TypedValueT, typename FunctionContextT>
+inline const auto Contains = StringMatchOperator<ContainsPredicate<TypedValueT>, TypedValueT, FunctionContextT>;
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Assert(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Bool, Optional<String>>("assert", args, nargs);
+  if (!args[0].ValueBool()) {
+    std::string message("Assertion failed");
+    if (nargs == 2) {
+      message += ": ";
+      message += args[1].ValueString();
+    }
+    message += ".";
+    throw FunctionRuntimeException(message);
+  }
+  return TypedValueT(args[0], ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Counter(const TypedValueT *args, int64_t nargs, const FunctionContextT &context) {
+  FType<TypedValueT, String, Integer, Optional<NonZeroInteger>>("counter", args, nargs);
+  int64_t step = 1;
+  if (nargs == 3) {
+    step = args[2].ValueInt();
+  }
+
+  auto [it, inserted] = context.counters->emplace(args[0].ValueString(), args[1].ValueInt());
+  auto value = it->second;
+  it->second += step;
+
+  return TypedValueT(value, context.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Id(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, Edge>>("id", args, nargs);
+  const auto &arg = args[0];
+  if (arg.IsNull()) {
+    return TypedValueT(ctx.memory);
+  }
+  return TypedValueT(static_cast<int64_t>(arg.ValueEdge().CypherId()), ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT ToString(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, String, Number, Date, LocalTime, LocalDateTime, Duration, Bool>>("toString", args, nargs);
+  const auto &arg = args[0];
+  if (arg.IsNull()) {
+    return TypedValueT(ctx.memory);
+  }
+  if (arg.IsString()) {
+    return TypedValueT(arg, ctx.memory);
+  }
+  if (arg.IsInt()) {
+    // TODO: This is making a pointless copy of std::string, we may want to
+    // use a different conversion to string
+    return TypedValueT(std::to_string(arg.ValueInt()), ctx.memory);
+  }
+  if (arg.IsDouble()) {
+    return TypedValueT(std::to_string(arg.ValueDouble()), ctx.memory);
+  }
+  if (arg.IsDate()) {
+    return TypedValueT(arg.ValueDate().ToString(), ctx.memory);
+  }
+  if (arg.IsLocalTime()) {
+    return TypedValueT(arg.ValueLocalTime().ToString(), ctx.memory);
+  }
+  if (arg.IsLocalDateTime()) {
+    return TypedValueT(arg.ValueLocalDateTime().ToString(), ctx.memory);
+  }
+  if (arg.IsDuration()) {
+    return TypedValueT(arg.ValueDuration().ToString(), ctx.memory);
+  }
+
+  return TypedValueT(arg.ValueBool() ? "true" : "false", ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Timestamp(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Optional<Or<Date, LocalTime, LocalDateTime, Duration>>>("timestamp", args, nargs);
+  const auto &arg = *args;
+  if (arg.IsDate()) {
+    return TypedValueT(arg.ValueDate().MicrosecondsSinceEpoch(), ctx.memory);
+  }
+  if (arg.IsLocalTime()) {
+    return TypedValueT(arg.ValueLocalTime().MicrosecondsSinceEpoch(), ctx.memory);
+  }
+  if (arg.IsLocalDateTime()) {
+    return TypedValueT(arg.ValueLocalDateTime().MicrosecondsSinceEpoch(), ctx.memory);
+  }
+  if (arg.IsDuration()) {
+    return TypedValueT(arg.ValueDuration().microseconds, ctx.memory);
+  }
+  return TypedValueT(ctx.timestamp, ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Left(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, String>, Or<Null, NonNegativeInteger>>("left", args, nargs);
+  if (args[0].IsNull() || args[1].IsNull()) return TypedValueT(ctx.memory);
+  return TypedValueT(utils::Substr(args[0].ValueString(), 0, args[1].ValueInt()), ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Right(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, String>, Or<Null, NonNegativeInteger>>("right", args, nargs);
+  if (args[0].IsNull() || args[1].IsNull()) return TypedValueT(ctx.memory);
+  const auto &str = args[0].ValueString();
+  auto len = args[1].ValueInt();
+  return len <= str.size() ? TypedValueT(utils::Substr(str, str.size() - len, len), ctx.memory)
+                           : TypedValueT(str, ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT CallStringFunction(
+    const TypedValueT *args, int64_t nargs, utils::MemoryResource *memory, const char *name,
+    std::function<typename TypedValueT::TString(const typename TypedValueT::TString &)> fun) {
+  FType<TypedValueT, Or<Null, String>>(name, args, nargs);
+  if (args[0].IsNull()) return TypedValueT(memory);
+  return TypedValueT(fun(args[0].ValueString()), memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT LTrim(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  return CallStringFunction<TypedValueT, FunctionContextT>(args, nargs, ctx.memory, "lTrim", [&](const auto &str) {
+    return typename TypedValueT::TString(utils::LTrim(str), ctx.memory);
+  });
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT RTrim(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  return CallStringFunction<TypedValueT, FunctionContextT>(args, nargs, ctx.memory, "rTrim", [&](const auto &str) {
+    return typename TypedValueT::TString(utils::RTrim(str), ctx.memory);
+  });
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Trim(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  return CallStringFunction<TypedValueT, FunctionContextT>(args, nargs, ctx.memory, "trim", [&](const auto &str) {
+    return typename TypedValueT::TString(utils::Trim(str), ctx.memory);
+  });
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Reverse(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  return CallStringFunction<TypedValueT, FunctionContextT>(
+      args, nargs, ctx.memory, "reverse", [&](const auto &str) { return utils::Reversed(str, ctx.memory); });
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT ToLower(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  return CallStringFunction<TypedValueT, FunctionContextT>(args, nargs, ctx.memory, "toLower", [&](const auto &str) {
+    typename TypedValueT::TString res(ctx.memory);
+    utils::ToLowerCase(&res, str);
+    return res;
+  });
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT ToUpper(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  return CallStringFunction<TypedValueT, FunctionContextT>(args, nargs, ctx.memory, "toUpper", [&](const auto &str) {
+    typename TypedValueT::TString res(ctx.memory);
+    utils::ToUpperCase(&res, str);
+    return res;
+  });
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Replace(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, String>, Or<Null, String>, Or<Null, String>>("replace", args, nargs);
+  if (args[0].IsNull() || args[1].IsNull() || args[2].IsNull()) {
+    return TypedValueT(ctx.memory);
+  }
+  typename TypedValueT::TString replaced(ctx.memory);
+  utils::Replace(&replaced, args[0].ValueString(), args[1].ValueString(), args[2].ValueString());
+  return TypedValueT(std::move(replaced));
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Split(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, String>, Or<Null, String>>("split", args, nargs);
+  if (args[0].IsNull() || args[1].IsNull()) {
+    return TypedValueT(ctx.memory);
+  }
+  typename TypedValueT::TVector result(ctx.memory);
+  utils::Split(&result, args[0].ValueString(), args[1].ValueString());
+  return TypedValueT(std::move(result));
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Substring(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<Null, String>, NonNegativeInteger, Optional<NonNegativeInteger>>("substring", args, nargs);
+  if (args[0].IsNull()) return TypedValueT(ctx.memory);
+  const auto &str = args[0].ValueString();
+  auto start = args[1].ValueInt();
+  if (nargs == 2) return TypedValueT(utils::Substr(str, start), ctx.memory);
+  auto len = args[2].ValueInt();
+  return TypedValueT(utils::Substr(str, start, len), ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT ToByteString(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, String>("toByteString", args, nargs);
+  const auto &str = args[0].ValueString();
+  if (str.empty()) return TypedValueT("", ctx.memory);
+  if (!utils::StartsWith(str, "0x") && !utils::StartsWith(str, "0X")) {
+    throw FunctionRuntimeException("'toByteString' argument must start with '0x'");
+  }
+  const auto &hex_str = utils::Substr(str, 2);
+  auto read_hex = [](const char ch) -> unsigned char {
+    if (ch >= '0' && ch <= '9') return ch - '0';
+    if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
+    if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10;
+    throw FunctionRuntimeException("'toByteString' argument has an invalid character '{}'", ch);
+  };
+  utils::pmr::string bytes(ctx.memory);
+  bytes.reserve((1 + hex_str.size()) / 2);
+  size_t i = 0;
+  // Treat odd length hex string as having a leading zero.
+  if (hex_str.size() % 2) bytes.append(1, read_hex(hex_str[i++]));
+  for (; i < hex_str.size(); i += 2) {
+    unsigned char byte = read_hex(hex_str[i]) * 16U + read_hex(hex_str[i + 1]);
+    // MemcpyCast in case we are converting to a signed value, so as to avoid
+    // undefined behaviour.
+    bytes.append(1, utils::MemcpyCast<decltype(bytes)::value_type>(byte));
+  }
+  return TypedValueT(std::move(bytes));
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT FromByteString(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, String, Optional<PositiveInteger>>("fromByteString", args, nargs);
+  const auto &bytes = args[0].ValueString();
+  if (bytes.empty()) return TypedValueT("", ctx.memory);
+  size_t min_length = bytes.size();
+  if (nargs == 2) min_length = std::max(min_length, static_cast<size_t>(args[1].ValueInt()));
+  utils::pmr::string str(ctx.memory);
+  str.reserve(min_length * 2 + 2);
+  str.append("0x");
+  for (size_t pad = 0; pad < min_length - bytes.size(); ++pad) str.append(2, '0');
+  // Convert the bytes to a character string in hex representation.
+  // Unfortunately, we don't know whether the default `char` is signed or
+  // unsigned, so we have to work around any potential undefined behaviour when
+  // conversions between the 2 occur. That's why this function is more
+  // complicated than it should be.
+  auto to_hex = [](const unsigned char val) -> char {
+    unsigned char ch = val < 10U ? static_cast<unsigned char>('0') + val : static_cast<unsigned char>('a') + val - 10U;
+    return utils::MemcpyCast<char>(ch);
+  };
+  for (unsigned char byte : bytes) {
+    str.append(1, to_hex(byte / 16U));
+    str.append(1, to_hex(byte % 16U));
+  }
+  return TypedValueT(std::move(str));
+}
+
+template <typename T>
+concept IsNumberOrInteger = utils::SameAsAnyOf<T, Number, Integer>;
+
+template <IsNumberOrInteger ArgType>
+void MapNumericParameters(auto &parameter_mappings, const auto &input_parameters) {
+  for (const auto &[key, value] : input_parameters) {
+    if (auto it = parameter_mappings.find(key); it != parameter_mappings.end()) {
+      if (value.IsInt()) {
+        *it->second = value.ValueInt();
+      } else if (std::is_same_v<ArgType, Number> && value.IsDouble()) {
+        *it->second = value.ValueDouble();
+      } else {
+        std::string_view error = std::is_same_v<ArgType, Integer> ? "an integer." : "a numeric value.";
+        throw FunctionRuntimeException("Invalid value for key '{}'. Expected {}", key, error);
+      }
+    } else {
+      throw FunctionRuntimeException("Unknown key '{}'.", key);
+    }
+  }
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Date(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Optional<Or<String, Map>>>("date", args, nargs);
+  if (nargs == 0) {
+    return TypedValueT(utils::LocalDateTime(ctx.timestamp).date, ctx.memory);
+  }
+
+  if (args[0].IsString()) {
+    const auto &[date_parameters, is_extended] = utils::ParseDateParameters(args[0].ValueString());
+    return TypedValueT(utils::Date(date_parameters), ctx.memory);
+  }
+
+  utils::DateParameters date_parameters;
+
+  using namespace std::literals;
+  std::unordered_map parameter_mappings = {std::pair{"year"sv, &date_parameters.year},
+                                           std::pair{"month"sv, &date_parameters.month},
+                                           std::pair{"day"sv, &date_parameters.day}};
+
+  MapNumericParameters<Integer>(parameter_mappings, args[0].ValueMap());
+  return TypedValueT(utils::Date(date_parameters), ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT LocalTime(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Optional<Or<String, Map>>>("localtime", args, nargs);
+
+  if (nargs == 0) {
+    return TypedValueT(utils::LocalDateTime(ctx.timestamp).local_time, ctx.memory);
+  }
+
+  if (args[0].IsString()) {
+    const auto &[local_time_parameters, is_extended] = utils::ParseLocalTimeParameters(args[0].ValueString());
+    return TypedValueT(utils::LocalTime(local_time_parameters), ctx.memory);
+  }
+
+  utils::LocalTimeParameters local_time_parameters;
+
+  using namespace std::literals;
+  std::unordered_map parameter_mappings{
+      std::pair{"hour"sv, &local_time_parameters.hour},
+      std::pair{"minute"sv, &local_time_parameters.minute},
+      std::pair{"second"sv, &local_time_parameters.second},
+      std::pair{"millisecond"sv, &local_time_parameters.millisecond},
+      std::pair{"microsecond"sv, &local_time_parameters.microsecond},
+  };
+
+  MapNumericParameters<Integer>(parameter_mappings, args[0].ValueMap());
+  return TypedValueT(utils::LocalTime(local_time_parameters), ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT LocalDateTime(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Optional<Or<String, Map>>>("localdatetime", args, nargs);
+
+  if (nargs == 0) {
+    return TypedValueT(utils::LocalDateTime(ctx.timestamp), ctx.memory);
+  }
+
+  if (args[0].IsString()) {
+    const auto &[date_parameters, local_time_parameters] = ParseLocalDateTimeParameters(args[0].ValueString());
+    return TypedValueT(utils::LocalDateTime(date_parameters, local_time_parameters), ctx.memory);
+  }
+
+  utils::DateParameters date_parameters;
+  utils::LocalTimeParameters local_time_parameters;
+  using namespace std::literals;
+  std::unordered_map parameter_mappings{
+      std::pair{"year"sv, &date_parameters.year},
+      std::pair{"month"sv, &date_parameters.month},
+      std::pair{"day"sv, &date_parameters.day},
+      std::pair{"hour"sv, &local_time_parameters.hour},
+      std::pair{"minute"sv, &local_time_parameters.minute},
+      std::pair{"second"sv, &local_time_parameters.second},
+      std::pair{"millisecond"sv, &local_time_parameters.millisecond},
+      std::pair{"microsecond"sv, &local_time_parameters.microsecond},
+  };
+
+  MapNumericParameters<Integer>(parameter_mappings, args[0].ValueMap());
+  return TypedValueT(utils::LocalDateTime(date_parameters, local_time_parameters), ctx.memory);
+}
+
+template <typename TypedValueT, typename FunctionContextT>
+TypedValueT Duration(const TypedValueT *args, int64_t nargs, const FunctionContextT &ctx) {
+  FType<TypedValueT, Or<String, Map>>("duration", args, nargs);
+
+  if (args[0].IsString()) {
+    return TypedValueT(utils::Duration(ParseDurationParameters(args[0].ValueString())), ctx.memory);
+  }
+
+  utils::DurationParameters duration_parameters;
+  using namespace std::literals;
+  std::unordered_map parameter_mappings{std::pair{"day"sv, &duration_parameters.day},
+                                        std::pair{"hour"sv, &duration_parameters.hour},
+                                        std::pair{"minute"sv, &duration_parameters.minute},
+                                        std::pair{"second"sv, &duration_parameters.second},
+                                        std::pair{"millisecond"sv, &duration_parameters.millisecond},
+                                        std::pair{"microsecond"sv, &duration_parameters.microsecond}};
+  MapNumericParameters<Number>(parameter_mappings, args[0].ValueMap());
+  return TypedValueT(utils::Duration(duration_parameters), ctx.memory);
+}
+
+}  // namespace memgraph::functions::impl
+
+namespace memgraph::functions {
+
+template <typename TypedValueT, typename FunctionContextT, typename Tag, typename Conv>
+std::function<TypedValueT(const TypedValueT *arguments, int64_t num_arguments, const FunctionContextT &context)>
+NameToFunction(const std::string &function_name) {
+  // Scalar functions
+  if (function_name == "DEGREE") return functions::impl::Degree<TypedValueT, FunctionContextT, Tag>;
+  if (function_name == "INDEGREE") return functions::impl::InDegree<TypedValueT, FunctionContextT, Tag>;
+  if (function_name == "OUTDEGREE") return functions::impl::OutDegree<TypedValueT, FunctionContextT, Tag>;
+  if (function_name == "HEAD") return functions::impl::Head<TypedValueT, FunctionContextT>;
+  if (function_name == kId) return functions::impl::Id<TypedValueT, FunctionContextT>;
+  if (function_name == "LAST") return functions::impl::Last<TypedValueT, FunctionContextT>;
+  if (function_name == "PROPERTIES") return functions::impl::Properties<TypedValueT, FunctionContextT, Tag, Conv>;
+  if (function_name == "SIZE") return functions::impl::Size<TypedValueT, FunctionContextT>;
+  if (function_name == "TIMESTAMP") return functions::impl::Timestamp<TypedValueT, FunctionContextT>;
+  if (function_name == "TOBOOLEAN") return functions::impl::ToBoolean<TypedValueT, FunctionContextT>;
+  if (function_name == "TOFLOAT") return functions::impl::ToFloat<TypedValueT, FunctionContextT>;
+  if (function_name == "TOINTEGER") return functions::impl::ToInteger<TypedValueT, FunctionContextT>;
+  if (function_name == "TYPE") return functions::impl::Type<TypedValueT, FunctionContextT>;
+  if (function_name == "VALUETYPE") return functions::impl::ValueType<TypedValueT, FunctionContextT>;
+  // Only on QE
+  if constexpr (std::is_same_v<Tag, QueryEngineTag>) {
+    if (function_name == "STARTNODE") return functions::impl::StartNode<TypedValueT, FunctionContextT, Conv>;
+    if (function_name == "ENDNODE") return functions::impl::EndNode<TypedValueT, FunctionContextT>;
+  }
+
+  // List functions
+  if (function_name == "LABELS") return functions::impl::Labels<TypedValueT, FunctionContextT, Tag>;
+  if (function_name == "NODES") return functions::impl::Nodes<TypedValueT, FunctionContextT>;
+  if (function_name == "RANGE") return functions::impl::Range<TypedValueT, FunctionContextT>;
+  if (function_name == "RELATIONSHIPS") return functions::impl::Relationships<TypedValueT, FunctionContextT>;
+  if (function_name == "TAIL") return functions::impl::Tail<TypedValueT, FunctionContextT>;
+  if (function_name == "UNIFORMSAMPLE") return functions::impl::UniformSample<TypedValueT, FunctionContextT>;
+
+  // Mathematical functions - numeric
+  if (function_name == "ABS") return functions::impl::Abs<TypedValueT, FunctionContextT>;
+  if (function_name == "CEIL") return functions::impl::Ceil<TypedValueT, FunctionContextT>;
+  if (function_name == "FLOOR") return functions::impl::Floor<TypedValueT, FunctionContextT>;
+  if (function_name == "RAND") return functions::impl::Rand<TypedValueT, FunctionContextT>;
+  if (function_name == "ROUND") return functions::impl::Round<TypedValueT, FunctionContextT>;
+  if (function_name == "SIGN") return functions::impl::Sign<TypedValueT, FunctionContextT>;
+
+  // Mathematical functions - logarithmic
+  if (function_name == "E") return functions::impl::E<TypedValueT, FunctionContextT>;
+  if (function_name == "EXP") return functions::impl::Exp<TypedValueT, FunctionContextT>;
+  if (function_name == "LOG") return functions::impl::Log<TypedValueT, FunctionContextT>;
+  if (function_name == "LOG10") return functions::impl::Log10<TypedValueT, FunctionContextT>;
+  if (function_name == "SQRT") return functions::impl::Sqrt<TypedValueT, FunctionContextT>;
+
+  // Mathematical functions - trigonometric
+  if (function_name == "ACOS") return functions::impl::Acos<TypedValueT, FunctionContextT>;
+  if (function_name == "ASIN") return functions::impl::Asin<TypedValueT, FunctionContextT>;
+  if (function_name == "ATAN") return functions::impl::Atan<TypedValueT, FunctionContextT>;
+  if (function_name == "ATAN2") return functions::impl::Atan2<TypedValueT, FunctionContextT>;
+  if (function_name == "COS") return functions::impl::Cos<TypedValueT, FunctionContextT>;
+  if (function_name == "PI") return functions::impl::Pi<TypedValueT, FunctionContextT>;
+  if (function_name == "SIN") return functions::impl::Sin<TypedValueT, FunctionContextT>;
+  if (function_name == "TAN") return functions::impl::Tan<TypedValueT, FunctionContextT>;
+
+  // String functions
+  if (function_name == kContains) return functions::impl::Contains<TypedValueT, FunctionContextT>;
+  if (function_name == kEndsWith) return functions::impl::EndsWith<TypedValueT, FunctionContextT>;
+  if (function_name == "LEFT") return functions::impl::Left<TypedValueT, FunctionContextT>;
+  if (function_name == "LTRIM") return functions::impl::LTrim<TypedValueT, FunctionContextT>;
+  if (function_name == "REPLACE") return functions::impl::Replace<TypedValueT, FunctionContextT>;
+  if (function_name == "REVERSE") return functions::impl::Reverse<TypedValueT, FunctionContextT>;
+  if (function_name == "RIGHT") return functions::impl::Right<TypedValueT, FunctionContextT>;
+  if (function_name == "RTRIM") return functions::impl::RTrim<TypedValueT, FunctionContextT>;
+  if (function_name == "SPLIT") return functions::impl::Split<TypedValueT, FunctionContextT>;
+  if (function_name == kStartsWith) return functions::impl::StartsWith<TypedValueT, FunctionContextT>;
+  if (function_name == "SUBSTRING") return functions::impl::Substring<TypedValueT, FunctionContextT>;
+  if (function_name == "TOLOWER") return functions::impl::ToLower<TypedValueT, FunctionContextT>;
+  if (function_name == "TOSTRING") return functions::impl::ToString<TypedValueT, FunctionContextT>;
+  if (function_name == "TOUPPER") return functions::impl::ToUpper<TypedValueT, FunctionContextT>;
+  if (function_name == "TRIM") return functions::impl::Trim<TypedValueT, FunctionContextT>;
+
+  // Memgraph specific functions
+  if (function_name == "ASSERT") return functions::impl::Assert<TypedValueT, FunctionContextT>;
+  if (function_name == "TOBYTESTRING") return functions::impl::ToByteString<TypedValueT, FunctionContextT>;
+  if (function_name == "FROMBYTESTRING") return functions::impl::FromByteString<TypedValueT, FunctionContextT>;
+  // Only on QE
+  if constexpr (std::is_same_v<Tag, QueryEngineTag>) {
+    if (function_name == "COUNTER") return functions::impl::Counter<TypedValueT, FunctionContextT>;
+  }
+
+  // Functions for temporal types
+  if (function_name == "DATE") return functions::impl::Date<TypedValueT, FunctionContextT>;
+  if (function_name == "LOCALTIME") return functions::impl::LocalTime<TypedValueT, FunctionContextT>;
+  if (function_name == "LOCALDATETIME") return functions::impl::LocalDateTime<TypedValueT, FunctionContextT>;
+  if (function_name == "DURATION") return functions::impl::Duration<TypedValueT, FunctionContextT>;
+
+  return nullptr;
+}
+
+}  // namespace memgraph::functions
diff --git a/src/query/v2/CMakeLists.txt b/src/query/v2/CMakeLists.txt
index 3c3f780c8..3ecaf63f9 100644
--- a/src/query/v2/CMakeLists.txt
+++ b/src/query/v2/CMakeLists.txt
@@ -11,7 +11,6 @@ set(mg_query_v2_sources
     cypher_query_interpreter.cpp
     frontend/semantic/required_privileges.cpp
     frontend/stripped.cpp
-    interpret/awesome_memgraph_functions.cpp
     interpreter.cpp
     metadata.cpp
     plan/operator.cpp
@@ -34,7 +33,7 @@ target_include_directories(mg-query-v2 PUBLIC ${CMAKE_SOURCE_DIR}/include)
 target_include_directories(mg-query-v2 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/bindings)
 target_link_libraries(mg-query-v2 dl cppitertools Boost::headers)
 target_link_libraries(mg-query-v2 mg-integrations-pulsar mg-integrations-kafka mg-storage-v3 mg-license mg-utils mg-kvstore mg-memory mg-coordinator)
-target_link_libraries(mg-query-v2 mg-expr)
+target_link_libraries(mg-query-v2 mg-expr mg-functions)
 
 if(NOT "${MG_PYTHON_PATH}" STREQUAL "")
     set(Python3_ROOT_DIR "${MG_PYTHON_PATH}")
diff --git a/src/query/v2/accessors.cpp b/src/query/v2/accessors.cpp
index 4f6e0d0d7..a784b0103 100644
--- a/src/query/v2/accessors.cpp
+++ b/src/query/v2/accessors.cpp
@@ -23,11 +23,12 @@ EdgeTypeId EdgeAccessor::EdgeType() const { return edge.type.id; }
 const std::vector<std::pair<PropertyId, Value>> &EdgeAccessor::Properties() const { return edge.properties; }
 
 Value EdgeAccessor::GetProperty(const std::string &prop_name) const {
-  auto prop_id = manager_->NameToProperty(prop_name);
-  auto it = std::find_if(edge.properties.begin(), edge.properties.end(), [&](auto &pr) { return prop_id == pr.first; });
-  if (it == edge.properties.end()) {
+  auto maybe_prop = manager_->MaybeNameToProperty(prop_name);
+  if (!maybe_prop) {
     return {};
   }
+  const auto prop_id = *maybe_prop;
+  auto it = std::find_if(edge.properties.begin(), edge.properties.end(), [&](auto &pr) { return prop_id == pr.first; });
   return it->second;
 }
 
@@ -35,6 +36,8 @@ const Edge &EdgeAccessor::GetEdge() const { return edge; }
 
 bool EdgeAccessor::IsCycle() const { return edge.src == edge.dst; };
 
+size_t EdgeAccessor::CypherId() const { return edge.id.gid; }
+
 VertexAccessor EdgeAccessor::To() const {
   return VertexAccessor(Vertex{edge.dst}, std::vector<std::pair<PropertyId, msgs::Value>>{}, manager_);
 }
@@ -88,7 +91,11 @@ Value VertexAccessor::GetProperty(PropertyId prop_id) const {
 
 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
 Value VertexAccessor::GetProperty(const std::string &prop_name) const {
-  return GetProperty(manager_->NameToProperty(prop_name));
+  auto maybe_prop = manager_->MaybeNameToProperty(prop_name);
+  if (!maybe_prop) {
+    return {};
+  }
+  return GetProperty(*maybe_prop);
 }
 
 msgs::Vertex VertexAccessor::GetVertex() const { return vertex; }
diff --git a/src/query/v2/accessors.hpp b/src/query/v2/accessors.hpp
index ca7ec999d..48578d217 100644
--- a/src/query/v2/accessors.hpp
+++ b/src/query/v2/accessors.hpp
@@ -53,12 +53,7 @@ class EdgeAccessor final {
 
   [[nodiscard]] bool IsCycle() const;
 
-  // Dummy function
-  // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
-  [[nodiscard]] size_t CypherId() const { return 10; }
-
-  //  bool HasSrcAccessor const { return src == nullptr; }
-  //  bool HasDstAccessor const { return dst == nullptr; }
+  [[nodiscard]] size_t CypherId() const;
 
   [[nodiscard]] VertexAccessor To() const;
   [[nodiscard]] VertexAccessor From() const;
@@ -98,48 +93,11 @@ class VertexAccessor final {
 
   [[nodiscard]] msgs::Vertex GetVertex() const;
 
-  // Dummy function
   // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
-  [[nodiscard]] size_t CypherId() const { return 10; }
+  [[nodiscard]] size_t InDegree() const { throw utils::NotYetImplemented("InDegree() not yet implemented"); }
 
-  //  auto InEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types) const
-  //      -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.InEdges(view)))> {
-  //    auto maybe_edges = impl_.InEdges(view, edge_types);
-  //    if (maybe_edges.HasError()) return maybe_edges.GetError();
-  //    return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
-  //  }
-  //
-  //  auto InEdges(storage::View view) const { return InEdges(view, {}); }
-  //
-  //  auto InEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types, const VertexAccessor &dest)
-  //  const
-  //      -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.InEdges(view)))> {
-  //    auto maybe_edges = impl_.InEdges(view, edge_types, &dest.impl_);
-  //    if (maybe_edges.HasError()) return maybe_edges.GetError();
-  //    return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
-  //  }
-  //
-  //  auto OutEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types) const
-  //      -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.OutEdges(view)))> {
-  //    auto maybe_edges = impl_.OutEdges(view, edge_types);
-  //    if (maybe_edges.HasError()) return maybe_edges.GetError();
-  //    return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
-  //  }
-  //
-  //  auto OutEdges(storage::View view) const { return OutEdges(view, {}); }
-  //
-  //  auto OutEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types,
-  //                const VertexAccessor &dest) const
-  //      -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.OutEdges(view)))> {
-  //    auto maybe_edges = impl_.OutEdges(view, edge_types, &dest.impl_);
-  //    if (maybe_edges.HasError()) return maybe_edges.GetError();
-  //    return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
-  //  }
-
-  //  storage::Result<size_t> InDegree(storage::View view) const { return impl_.InDegree(view); }
-  //
-  //  storage::Result<size_t> OutDegree(storage::View view) const { return impl_.OutDegree(view); }
-  //
+  // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
+  [[nodiscard]] size_t OutDegree() const { throw utils::NotYetImplemented("OutDegree() not yet implemented"); }
 
   friend bool operator==(const VertexAccessor &lhs, const VertexAccessor &rhs) {
     return lhs.vertex == rhs.vertex && lhs.properties == rhs.properties;
@@ -153,10 +111,6 @@ class VertexAccessor final {
   const ShardRequestManagerInterface *manager_;
 };
 
-// inline VertexAccessor EdgeAccessor::To() const { return VertexAccessor(impl_.ToVertex()); }
-
-// inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.FromVertex()); }
-
 // Highly mocked interface. Won't work if used.
 class Path {
  public:
@@ -197,7 +151,14 @@ class Path {
   friend bool operator==(const Path & /*lhs*/, const Path & /*rhs*/) { return true; };
   utils::MemoryResource *GetMemoryResource() { return mem; }
 
+  auto &vertices() { return vertices_; }
+  auto &edges() { return edges_; }
+  const auto &vertices() const { return vertices_; }
+  const auto &edges() const { return edges_; }
+
  private:
+  std::vector<VertexAccessor> vertices_;
+  std::vector<EdgeAccessor> edges_;
   utils::MemoryResource *mem = utils::NewDeleteResource();
 };
 }  // namespace memgraph::query::v2::accessors
diff --git a/src/query/v2/bindings/eval.hpp b/src/query/v2/bindings/eval.hpp
index 584e88922..02e27b22a 100644
--- a/src/query/v2/bindings/eval.hpp
+++ b/src/query/v2/bindings/eval.hpp
@@ -28,7 +28,6 @@ namespace memgraph::query::v2 {
 
 class ShardRequestManagerInterface;
 
-inline const auto lam = [](const auto &val) { return ValueToTypedValue(val); };
 namespace detail {
 class Callable {
  public:
diff --git a/src/query/v2/conversions.hpp b/src/query/v2/conversions.hpp
index c18ba8cc0..a1db5ed17 100644
--- a/src/query/v2/conversions.hpp
+++ b/src/query/v2/conversions.hpp
@@ -56,6 +56,10 @@ inline TypedValue ValueToTypedValue(const msgs::Value &value, ShardRequestManage
   throw std::runtime_error("Incorrect type in conversion");
 }
 
+inline const auto ValueToTypedValueFunctor = [](const msgs::Value &value, ShardRequestManagerInterface *manager) {
+  return ValueToTypedValue(value, manager);
+};
+
 inline msgs::Value TypedValueToValue(const TypedValue &value) {
   using Value = msgs::Value;
   switch (value.type()) {
diff --git a/src/query/v2/frontend/ast/ast.lcp b/src/query/v2/frontend/ast/ast.lcp
index e30e623ea..dadcc9fa7 100644
--- a/src/query/v2/frontend/ast/ast.lcp
+++ b/src/query/v2/frontend/ast/ast.lcp
@@ -20,11 +20,13 @@
 #include "query/v2/bindings/ast_visitor.hpp"
 #include "common/types.hpp"
 #include "query/v2/bindings/symbol.hpp"
-#include "query/v2/interpret/awesome_memgraph_functions.hpp"
+#include "functions/awesome_memgraph_functions.hpp"
 #include "query/v2/bindings/typed_value.hpp"
 #include "query/v2/db_accessor.hpp"
 #include "query/v2/path.hpp"
+#include "query/v2/shard_request_manager.hpp"
 #include "utils/typeinfo.hpp"
+#include "query/v2/conversions.hpp"
 
 cpp<#
 
@@ -836,13 +838,15 @@ cpp<#
               :slk-load (slk-load-ast-vector "Expression"))
    (function-name "std::string" :scope :public)
    (function "std::function<TypedValue(const TypedValue *, int64_t,
-                                       const FunctionContext &)>"
+                                       const functions::FunctionContext<ShardRequestManagerInterface> &)>"
              :scope :public
              :dont-save t
              :clone :copy
              :slk-load (lambda (member)
                         #>cpp
-                        self->${member} = query::v2::NameToFunction(self->function_name_);
+                        self->${member} = functions::NameToFunction<TypedValue,
+                                            functions::FunctionContext<ShardRequestManagerInterface>,
+                                            functions::QueryEngineTag, decltype(ValueToTypedValueFunctor)>(self->function_name_);
                         cpp<#)))
   (:public
     #>cpp
@@ -865,7 +869,8 @@ cpp<#
              const std::vector<Expression *> &arguments)
         : arguments_(arguments),
           function_name_(function_name),
-          function_(NameToFunction(function_name_)) {
+          function_(functions::NameToFunction<TypedValue, functions::FunctionContext<ShardRequestManagerInterface>,
+                                              functions::QueryEngineTag, decltype(ValueToTypedValueFunctor)>(function_name_)) {
       if (!function_) {
         throw SemanticException("Function '{}' doesn't exist.", function_name);
       }
diff --git a/src/query/v2/interpret/awesome_memgraph_functions.cpp b/src/query/v2/interpret/awesome_memgraph_functions.cpp
deleted file mode 100644
index 8768736b2..000000000
--- a/src/query/v2/interpret/awesome_memgraph_functions.cpp
+++ /dev/null
@@ -1,1108 +0,0 @@
-// Copyright 2022 Memgraph Ltd.
-//
-// Use of this software is governed by the Business Source License
-// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
-// License, and you may not use this file except in compliance with the Business Source License.
-//
-// As of the Change Date specified in that file, in accordance with
-// the Business Source License, use of this software will be governed
-// by the Apache License, Version 2.0, included in the file
-// licenses/APL.txt.
-
-#include "query/v2/interpret/awesome_memgraph_functions.hpp"
-
-#include <algorithm>
-#include <cctype>
-#include <cmath>
-#include <cstdlib>
-#include <functional>
-#include <random>
-#include <string_view>
-#include <type_traits>
-
-#include "query/v2/bindings/typed_value.hpp"
-#include "query/v2/conversions.hpp"
-#include "query/v2/exceptions.hpp"
-#include "query/v2/shard_request_manager.hpp"
-#include "storage/v3/conversions.hpp"
-#include "utils/string.hpp"
-#include "utils/temporal.hpp"
-
-namespace memgraph::query::v2 {
-namespace {
-
-////////////////////////////////////////////////////////////////////////////////
-// eDSL using template magic for describing a type of an awesome memgraph
-// function and checking if the passed in arguments match the description.
-//
-// To use the type checking eDSL, you should put a `FType` invocation in the
-// body of your awesome Memgraph function. `FType` takes type arguments as the
-// description of the function type signature. Each runtime argument will be
-// checked in order corresponding to given compile time type arguments. These
-// type arguments can come in two forms:
-//
-//   * final, primitive type descriptor and
-//   * combinator type descriptor.
-//
-// The primitive type descriptors are defined as empty structs, they are right
-// below this documentation.
-//
-// Combinator type descriptors are defined as structs taking additional type
-// parameters, you can find these further below in the implementation. Of
-// primary interest are `Or` and `Optional` type combinators.
-//
-// With `Or` you can describe that an argument can be any of the types listed in
-// `Or`. For example, `Or<Null, Bool, Integer>` allows an argument to be either
-// `Null` or a boolean or an integer.
-//
-// The `Optional` combinator is used to define optional arguments to a function.
-// These must come as the last positional arguments. Naturally, you can use `Or`
-// inside `Optional`. So for example, `Optional<Or<Null, Bool>, Integer>`
-// describes that a function takes 2 optional arguments. The 1st one must be
-// either a `Null` or a boolean, while the 2nd one must be an integer. The type
-// signature check will succeed in the following cases.
-//
-//   * No optional arguments were supplied.
-//   * One argument was supplied and it passes `Or<Null, Bool>` check.
-//   * Two arguments were supplied, the 1st one passes `Or<Null, Bool>` check
-//     and the 2nd one passes `Integer` check.
-//
-// Runtime arguments to `FType` are: function name, pointer to arguments and the
-// number of received arguments.
-//
-// Full example.
-//
-//     FType<Or<Null, String>, NonNegativeInteger,
-//           Optional<NonNegativeInteger>>("substring", args, nargs);
-//
-// The above will check that `substring` function received the 2 required
-// arguments. Optionally, the function may take a 3rd argument. The 1st argument
-// must be either a `Null` or a character string. The 2nd argument is required
-// to be a non-negative integer. If the 3rd argument was supplied, it will also
-// be checked that it is a non-negative integer. If any of these checks fail,
-// `FType` will throw a `QueryRuntimeException` with an appropriate error
-// message.
-////////////////////////////////////////////////////////////////////////////////
-
-struct Null {};
-struct Bool {};
-struct Integer {};
-struct PositiveInteger {};
-struct NonZeroInteger {};
-struct NonNegativeInteger {};
-struct Double {};
-struct Number {};
-struct List {};
-struct String {};
-struct Map {};
-struct Edge {};
-struct Vertex {};
-struct Path {};
-struct Date {};
-struct LocalTime {};
-struct LocalDateTime {};
-struct Duration {};
-
-template <class ArgType>
-bool ArgIsType(const TypedValue &arg) {
-  if constexpr (std::is_same_v<ArgType, Null>) {
-    return arg.IsNull();
-  } else if constexpr (std::is_same_v<ArgType, Bool>) {
-    return arg.IsBool();
-  } else if constexpr (std::is_same_v<ArgType, Integer>) {
-    return arg.IsInt();
-  } else if constexpr (std::is_same_v<ArgType, PositiveInteger>) {
-    return arg.IsInt() && arg.ValueInt() > 0;
-  } else if constexpr (std::is_same_v<ArgType, NonZeroInteger>) {
-    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>) {
-    return arg.IsList();
-  } else if constexpr (std::is_same_v<ArgType, String>) {
-    return arg.IsString();
-  } else if constexpr (std::is_same_v<ArgType, Map>) {
-    return arg.IsMap();
-  } else if constexpr (std::is_same_v<ArgType, Vertex>) {
-    return arg.IsVertex();
-  } else if constexpr (std::is_same_v<ArgType, Edge>) {
-    return arg.IsEdge();
-  } else if constexpr (std::is_same_v<ArgType, Path>) {
-    return arg.IsPath();
-  } else if constexpr (std::is_same_v<ArgType, Date>) {
-    return arg.IsDate();
-  } else if constexpr (std::is_same_v<ArgType, LocalTime>) {
-    return arg.IsLocalTime();
-  } else if constexpr (std::is_same_v<ArgType, LocalDateTime>) {
-    return arg.IsLocalDateTime();
-  } else if constexpr (std::is_same_v<ArgType, Duration>) {
-    return arg.IsDuration();
-  } else if constexpr (std::is_same_v<ArgType, void>) {
-    return true;
-  } else {
-    static_assert(std::is_same_v<ArgType, Null>, "Unknown ArgType");
-  }
-  return false;
-}
-
-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>) {
-    return "boolean";
-  } else if constexpr (std::is_same_v<ArgType, Integer>) {
-    return "integer";
-  } else if constexpr (std::is_same_v<ArgType, PositiveInteger>) {
-    return "positive integer";
-  } else if constexpr (std::is_same_v<ArgType, NonZeroInteger>) {
-    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>) {
-    return "list";
-  } else if constexpr (std::is_same_v<ArgType, String>) {
-    return "string";
-  } else if constexpr (std::is_same_v<ArgType, Map>) {
-    return "map";
-  } else if constexpr (std::is_same_v<ArgType, Vertex>) {
-    return "node";
-  } else if constexpr (std::is_same_v<ArgType, Edge>) {
-    return "relationship";
-  } else if constexpr (std::is_same_v<ArgType, Path>) {
-    return "path";
-  } else if constexpr (std::is_same_v<ArgType, void>) {
-    return "void";
-  } else if constexpr (std::is_same_v<ArgType, Date>) {
-    return "Date";
-  } else if constexpr (std::is_same_v<ArgType, LocalTime>) {
-    return "LocalTime";
-  } else if constexpr (std::is_same_v<ArgType, LocalDateTime>) {
-    return "LocalDateTime";
-  } else if constexpr (std::is_same_v<ArgType, Duration>) {
-    return "Duration";
-  } else {
-    static_assert(std::is_same_v<ArgType, Null>, "Unknown ArgType");
-  }
-  return "<unknown-type>";
-}
-
-template <class... ArgType>
-struct Or;
-
-template <class ArgType>
-struct Or<ArgType> {
-  static bool Check(const TypedValue &arg) { return ArgIsType<ArgType>(arg); }
-
-  static std::string TypeNames() { return ArgTypeName<ArgType>(); }
-};
-
-template <class ArgType, class... ArgTypes>
-struct Or<ArgType, ArgTypes...> {
-  static bool Check(const TypedValue &arg) {
-    if (ArgIsType<ArgType>(arg)) return true;
-    return Or<ArgTypes...>::Check(arg);
-  }
-
-  static std::string TypeNames() {
-    if constexpr (sizeof...(ArgTypes) > 1) {
-      return fmt::format("'{}', {}", ArgTypeName<ArgType>(), Or<ArgTypes...>::TypeNames());
-    } else {
-      return fmt::format("'{}' or '{}'", ArgTypeName<ArgType>(), Or<ArgTypes...>::TypeNames());
-    }
-  }
-};
-
-template <class T>
-struct IsOrType {
-  static constexpr bool value = false;
-};
-
-template <class... ArgTypes>
-struct IsOrType<Or<ArgTypes...>> {
-  static constexpr bool value = true;
-};
-
-template <class... ArgTypes>
-struct Optional;
-
-template <class ArgType>
-struct Optional<ArgType> {
-  static constexpr size_t size = 1;
-
-  static void Check(const char *name, const TypedValue *args, int64_t nargs, int64_t pos) {
-    if (nargs == 0) return;
-    const TypedValue &arg = args[0];
-    if constexpr (IsOrType<ArgType>::value) {
-      if (!ArgType::Check(arg)) {
-        throw QueryRuntimeException("Optional '{}' argument at position {} must be either {}.", name, pos,
-                                    ArgType::TypeNames());
-      }
-    } else {
-      if (!ArgIsType<ArgType>(arg))
-        throw QueryRuntimeException("Optional '{}' argument at position {} must be '{}'.", name, pos,
-                                    ArgTypeName<ArgType>());
-    }
-  }
-};
-
-template <class ArgType, class... ArgTypes>
-struct Optional<ArgType, ArgTypes...> {
-  static constexpr size_t size = 1 + sizeof...(ArgTypes);
-
-  static void Check(const char *name, const TypedValue *args, int64_t nargs, int64_t pos) {
-    if (nargs == 0) return;
-    Optional<ArgType>::Check(name, args, nargs, pos);
-    Optional<ArgTypes...>::Check(name, args + 1, nargs - 1, pos + 1);
-  }
-};
-
-template <class T>
-struct IsOptional {
-  static constexpr bool value = false;
-};
-
-template <class... ArgTypes>
-struct IsOptional<Optional<ArgTypes...>> {
-  static constexpr bool value = true;
-};
-
-template <class ArgType, class... ArgTypes>
-constexpr size_t FTypeRequiredArgs() {
-  if constexpr (IsOptional<ArgType>::value) {
-    static_assert(sizeof...(ArgTypes) == 0, "Optional arguments must be last!");
-    return 0;
-  } else if constexpr (sizeof...(ArgTypes) == 0) {
-    return 1;
-  } else {
-    return 1U + FTypeRequiredArgs<ArgTypes...>();
-  }
-}
-
-template <class ArgType, class... ArgTypes>
-constexpr size_t FTypeOptionalArgs() {
-  if constexpr (IsOptional<ArgType>::value) {
-    static_assert(sizeof...(ArgTypes) == 0, "Optional arguments must be last!");
-    return ArgType::size;
-  } else if constexpr (sizeof...(ArgTypes) == 0) {
-    return 0;
-  } else {
-    return FTypeOptionalArgs<ArgTypes...>();
-  }
-}
-
-template <class ArgType, class... ArgTypes>
-void FType(const char *name, const TypedValue *args, int64_t nargs, int64_t pos = 1) {
-  if constexpr (std::is_same_v<ArgType, void>) {
-    if (nargs != 0) {
-      throw QueryRuntimeException("'{}' requires no arguments.", name);
-    }
-    return;
-  }
-  static constexpr int64_t required_args = FTypeRequiredArgs<ArgType, ArgTypes...>();
-  static constexpr int64_t optional_args = FTypeOptionalArgs<ArgType, ArgTypes...>();
-  static constexpr int64_t total_args = required_args + optional_args;
-  if constexpr (optional_args > 0) {
-    if (nargs < required_args || nargs > total_args) {
-      throw QueryRuntimeException("'{}' requires between {} and {} arguments.", name, required_args, total_args);
-    }
-  } else {
-    if (nargs != required_args) {
-      throw QueryRuntimeException("'{}' requires exactly {} {}.", name, required_args,
-                                  required_args == 1 ? "argument" : "arguments");
-    }
-  }
-  const TypedValue &arg = args[0];
-  if constexpr (IsOrType<ArgType>::value) {
-    if (!ArgType::Check(arg)) {
-      throw QueryRuntimeException("'{}' argument at position {} must be either {}.", name, pos, ArgType::TypeNames());
-    }
-  } else if constexpr (IsOptional<ArgType>::value) {
-    static_assert(sizeof...(ArgTypes) == 0, "Optional arguments must be last!");
-    ArgType::Check(name, args, nargs, pos);
-  } else {
-    if (!ArgIsType<ArgType>(arg)) {
-      throw QueryRuntimeException("'{}' argument at position {} must be '{}'", name, pos, ArgTypeName<ArgType>());
-    }
-  }
-  if constexpr (sizeof...(ArgTypes) > 0) {
-    FType<ArgTypes...>(name, args + 1, nargs - 1, pos + 1);
-  }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// END function type description eDSL
-////////////////////////////////////////////////////////////////////////////////
-
-// Predicate functions.
-// Neo4j has all, any, exists, none, single
-// Those functions are a little bit different since they take a filterExpression
-// as an argument.
-// There is all, any, none and single productions in opencypher grammar, but it
-// will be trivial to also add exists.
-// TODO: Implement this.
-
-// Scalar functions.
-// We don't have a way to implement id function since we don't store any. If it
-// is really neccessary we could probably map vlist* to id.
-// TODO: Implement length (it works on a path, but we didn't define path
-// structure yet).
-// TODO: Implement size(pattern), for example size((a)-[:X]-()) should return
-// number of results of this pattern. I don't think we will ever do this.
-// TODO: Implement rest of the list functions.
-// TODO: Implement degrees, haversin, radians
-// TODO: Implement spatial functions
-
-TypedValue EndNode(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, Edge>>("endNode", args, nargs);
-  if (args[0].IsNull()) return TypedValue(ctx.memory);
-  return TypedValue(args[0].ValueEdge().To(), ctx.memory);
-}
-
-TypedValue Head(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, List>>("head", args, nargs);
-  if (args[0].IsNull()) return TypedValue(ctx.memory);
-  const auto &list = args[0].ValueList();
-  if (list.empty()) return TypedValue(ctx.memory);
-  return TypedValue(list[0], ctx.memory);
-}
-
-TypedValue Last(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, List>>("last", args, nargs);
-  if (args[0].IsNull()) return TypedValue(ctx.memory);
-  const auto &list = args[0].ValueList();
-  if (list.empty()) return TypedValue(ctx.memory);
-  return TypedValue(list.back(), ctx.memory);
-}
-
-TypedValue Properties(const TypedValue * /*args*/, int64_t /*nargs*/, const FunctionContext & /*ctx*/) { return {}; }
-
-TypedValue Size(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, List, String, Map, Path>>("size", args, nargs);
-  const auto &value = args[0];
-  if (value.IsNull()) {
-    return TypedValue(ctx.memory);
-  } else if (value.IsList()) {
-    return TypedValue(static_cast<int64_t>(value.ValueList().size()), ctx.memory);
-  } else if (value.IsString()) {
-    return TypedValue(static_cast<int64_t>(value.ValueString().size()), ctx.memory);
-  } else if (value.IsMap()) {
-    // neo4j doesn't implement size for map, but I don't see a good reason not
-    // to do it.
-    return TypedValue(static_cast<int64_t>(value.ValueMap().size()), ctx.memory);
-  } else {
-    // TODO(kostasrim) Fix the dummy return
-    return TypedValue(int64_t(0), ctx.memory);
-  }
-}
-
-TypedValue StartNode(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, Edge>>("startNode", args, nargs);
-  if (args[0].IsNull()) return TypedValue(ctx.memory);
-  return TypedValue(args[0].ValueEdge().From(), ctx.memory);
-}
-
-TypedValue ToBoolean(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, Bool, Integer, String>>("toBoolean", args, nargs);
-  const auto &value = args[0];
-  if (value.IsNull()) {
-    return TypedValue(ctx.memory);
-  } else if (value.IsBool()) {
-    return TypedValue(value.ValueBool(), ctx.memory);
-  } else if (value.IsInt()) {
-    return TypedValue(value.ValueInt() != 0L, ctx.memory);
-  } else {
-    auto s = utils::ToUpperCase(utils::Trim(value.ValueString()));
-    if (s == "TRUE") return TypedValue(true, ctx.memory);
-    if (s == "FALSE") return TypedValue(false, ctx.memory);
-    // I think this is just stupid and that exception should be thrown, but
-    // neo4j does it this way...
-    return TypedValue(ctx.memory);
-  }
-}
-
-TypedValue ToFloat(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, Number, String>>("toFloat", args, nargs);
-  const auto &value = args[0];
-  if (value.IsNull()) {
-    return TypedValue(ctx.memory);
-  } else if (value.IsInt()) {
-    return TypedValue(static_cast<double>(value.ValueInt()), ctx.memory);
-  } else if (value.IsDouble()) {
-    return TypedValue(value, ctx.memory);
-  } else {
-    try {
-      return TypedValue(utils::ParseDouble(utils::Trim(value.ValueString())), ctx.memory);
-    } catch (const utils::BasicException &) {
-      return TypedValue(ctx.memory);
-    }
-  }
-}
-
-TypedValue ToInteger(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, Bool, Number, String>>("toInteger", args, nargs);
-  const auto &value = args[0];
-  if (value.IsNull()) {
-    return TypedValue(ctx.memory);
-  } else if (value.IsBool()) {
-    return TypedValue(value.ValueBool() ? 1L : 0L, ctx.memory);
-  } else if (value.IsInt()) {
-    return TypedValue(value, ctx.memory);
-  } else if (value.IsDouble()) {
-    return TypedValue(static_cast<int64_t>(value.ValueDouble()), ctx.memory);
-  } else {
-    try {
-      // Yup, this is correct. String is valid if it has floating point
-      // number, then it is parsed and converted to int.
-      return TypedValue(static_cast<int64_t>(utils::ParseDouble(utils::Trim(value.ValueString()))), ctx.memory);
-    } catch (const utils::BasicException &) {
-      return TypedValue(ctx.memory);
-    }
-  }
-}
-
-TypedValue Type(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, Edge>>("type", args, nargs);
-  if (args[0].IsNull()) return TypedValue(ctx.memory);
-  return TypedValue(args[0].ValueEdge().EdgeType().AsInt(), 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);
-    case TypedValue::Type::Date:
-      return TypedValue("DATE", ctx.memory);
-    case TypedValue::Type::LocalTime:
-      return TypedValue("LOCAL_TIME", ctx.memory);
-    case TypedValue::Type::LocalDateTime:
-      return TypedValue("LOCAL_DATE_TIME", ctx.memory);
-    case TypedValue::Type::Duration:
-      return TypedValue("DURATION", ctx.memory);
-  }
-}
-
-TypedValue Range(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, Integer>, Or<Null, Integer>, Optional<Or<Null, NonZeroInteger>>>("range", args, nargs);
-  for (int64_t i = 0; i < nargs; ++i)
-    if (args[i].IsNull()) return TypedValue(ctx.memory);
-  auto lbound = args[0].ValueInt();
-  auto rbound = args[1].ValueInt();
-  int64_t step = nargs == 3 ? args[2].ValueInt() : 1;
-  TypedValue::TVector list(ctx.memory);
-  if (lbound <= rbound && step > 0) {
-    for (auto i = lbound; i <= rbound; i += step) {
-      list.emplace_back(i);
-    }
-  } else if (lbound >= rbound && step < 0) {
-    for (auto i = lbound; i >= rbound; i += step) {
-      list.emplace_back(i);
-    }
-  }
-  return TypedValue(std::move(list));
-}
-
-TypedValue Tail(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, List>>("tail", args, nargs);
-  if (args[0].IsNull()) return TypedValue(ctx.memory);
-  TypedValue::TVector list(args[0].ValueList(), ctx.memory);
-  if (list.empty()) return TypedValue(std::move(list));
-  list.erase(list.begin());
-  return TypedValue(std::move(list));
-}
-
-TypedValue UniformSample(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, List>, Or<Null, NonNegativeInteger>>("uniformSample", args, nargs);
-  static thread_local std::mt19937 pseudo_rand_gen_{std::random_device{}()};
-  if (args[0].IsNull() || args[1].IsNull()) return TypedValue(ctx.memory);
-  const auto &population = args[0].ValueList();
-  auto population_size = population.size();
-  if (population_size == 0) return TypedValue(ctx.memory);
-  auto desired_length = args[1].ValueInt();
-  std::uniform_int_distribution<uint64_t> rand_dist{0, population_size - 1};
-  TypedValue::TVector sampled(ctx.memory);
-  sampled.reserve(desired_length);
-  for (int64_t i = 0; i < desired_length; ++i) {
-    sampled.emplace_back(population[rand_dist(pseudo_rand_gen_)]);
-  }
-  return TypedValue(std::move(sampled));
-}
-
-TypedValue Abs(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, Number>>("abs", args, nargs);
-  const auto &value = args[0];
-  if (value.IsNull()) {
-    return TypedValue(ctx.memory);
-  } else if (value.IsInt()) {
-    return TypedValue(std::abs(value.ValueInt()), ctx.memory);
-  } else {
-    return TypedValue(std::abs(value.ValueDouble()), ctx.memory);
-  }
-}
-
-#define WRAP_CMATH_FLOAT_FUNCTION(name, lowercased_name)                               \
-  TypedValue name(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { \
-    FType<Or<Null, Number>>(#lowercased_name, args, nargs);                            \
-    const auto &value = args[0];                                                       \
-    if (value.IsNull()) {                                                              \
-      return TypedValue(ctx.memory);                                                   \
-    } else if (value.IsInt()) {                                                        \
-      return TypedValue(lowercased_name(value.ValueInt()), ctx.memory);                \
-    } else {                                                                           \
-      return TypedValue(lowercased_name(value.ValueDouble()), ctx.memory);             \
-    }                                                                                  \
-  }
-
-WRAP_CMATH_FLOAT_FUNCTION(Ceil, ceil)
-WRAP_CMATH_FLOAT_FUNCTION(Floor, floor)
-// We are not completely compatible with neoj4 in this function because,
-// neo4j rounds -0.5, -1.5, -2.5... to 0, -1, -2...
-WRAP_CMATH_FLOAT_FUNCTION(Round, round)
-WRAP_CMATH_FLOAT_FUNCTION(Exp, exp)
-WRAP_CMATH_FLOAT_FUNCTION(Log, log)
-WRAP_CMATH_FLOAT_FUNCTION(Log10, log10)
-WRAP_CMATH_FLOAT_FUNCTION(Sqrt, sqrt)
-WRAP_CMATH_FLOAT_FUNCTION(Acos, acos)
-WRAP_CMATH_FLOAT_FUNCTION(Asin, asin)
-WRAP_CMATH_FLOAT_FUNCTION(Atan, atan)
-WRAP_CMATH_FLOAT_FUNCTION(Cos, cos)
-WRAP_CMATH_FLOAT_FUNCTION(Sin, sin)
-WRAP_CMATH_FLOAT_FUNCTION(Tan, tan)
-
-#undef WRAP_CMATH_FLOAT_FUNCTION
-
-TypedValue Atan2(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, Number>, Or<Null, Number>>("atan2", args, nargs);
-  if (args[0].IsNull() || args[1].IsNull()) return TypedValue(ctx.memory);
-  auto to_double = [](const TypedValue &t) -> double {
-    if (t.IsInt()) {
-      return t.ValueInt();
-    } else {
-      return t.ValueDouble();
-    }
-  };
-  double y = to_double(args[0]);
-  double x = to_double(args[1]);
-  return TypedValue(atan2(y, x), ctx.memory);
-}
-
-TypedValue Sign(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, Number>>("sign", args, nargs);
-  auto sign = [&](auto x) { return TypedValue((0 < x) - (x < 0), ctx.memory); };
-  const auto &value = args[0];
-  if (value.IsNull()) {
-    return TypedValue(ctx.memory);
-  } else if (value.IsInt()) {
-    return sign(value.ValueInt());
-  } else {
-    return sign(value.ValueDouble());
-  }
-}
-
-TypedValue E(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<void>("e", args, nargs);
-  return TypedValue(M_E, ctx.memory);
-}
-
-TypedValue Pi(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<void>("pi", args, nargs);
-  return TypedValue(M_PI, ctx.memory);
-}
-
-TypedValue Rand(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<void>("rand", args, nargs);
-  static thread_local std::mt19937 pseudo_rand_gen_{std::random_device{}()};
-  static thread_local std::uniform_real_distribution<> rand_dist_{0, 1};
-  return TypedValue(rand_dist_(pseudo_rand_gen_), ctx.memory);
-}
-
-template <class TPredicate>
-TypedValue StringMatchOperator(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, String>, Or<Null, String>>(TPredicate::name, args, nargs);
-  if (args[0].IsNull() || args[1].IsNull()) return TypedValue(ctx.memory);
-  const auto &s1 = args[0].ValueString();
-  const auto &s2 = args[1].ValueString();
-  return TypedValue(TPredicate{}(s1, s2), ctx.memory);
-}
-
-// Check if s1 starts with s2.
-struct StartsWithPredicate {
-  static constexpr const char *name = "startsWith";
-  bool operator()(const TypedValue::TString &s1, const TypedValue::TString &s2) const {
-    if (s1.size() < s2.size()) return false;
-    return std::equal(s2.begin(), s2.end(), s1.begin());
-  }
-};
-auto StartsWith = StringMatchOperator<StartsWithPredicate>;
-
-// Check if s1 ends with s2.
-struct EndsWithPredicate {
-  static constexpr const char *name = "endsWith";
-  bool operator()(const TypedValue::TString &s1, const TypedValue::TString &s2) const {
-    if (s1.size() < s2.size()) return false;
-    return std::equal(s2.rbegin(), s2.rend(), s1.rbegin());
-  }
-};
-auto EndsWith = StringMatchOperator<EndsWithPredicate>;
-
-// Check if s1 contains s2.
-struct ContainsPredicate {
-  static constexpr const char *name = "contains";
-  bool operator()(const TypedValue::TString &s1, const TypedValue::TString &s2) const {
-    if (s1.size() < s2.size()) return false;
-    return s1.find(s2) != std::string::npos;
-  }
-};
-auto Contains = StringMatchOperator<ContainsPredicate>;
-
-TypedValue Assert(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Bool, Optional<String>>("assert", args, nargs);
-  if (!args[0].ValueBool()) {
-    std::string message("Assertion failed");
-    if (nargs == 2) {
-      message += ": ";
-      message += args[1].ValueString();
-    }
-    message += ".";
-    throw QueryRuntimeException(message);
-  }
-  return TypedValue(args[0], ctx.memory);
-}
-
-TypedValue Counter(const TypedValue *args, int64_t nargs, const FunctionContext &context) {
-  FType<String, Integer, Optional<NonZeroInteger>>("counter", args, nargs);
-  int64_t step = 1;
-  if (nargs == 3) {
-    step = args[2].ValueInt();
-  }
-
-  auto [it, inserted] = context.counters->emplace(args[0].ValueString(), args[1].ValueInt());
-  auto value = it->second;
-  it->second += step;
-
-  return TypedValue(value, context.memory);
-}
-
-TypedValue Id(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, Vertex, Edge>>("id", args, nargs);
-  const auto &arg = args[0];
-  if (arg.IsNull()) {
-    return TypedValue(ctx.memory);
-  } else if (arg.IsVertex()) {
-    return TypedValue(int64_t(arg.ValueVertex().CypherId()), ctx.memory);
-  } else {
-    return TypedValue(int64_t(arg.ValueEdge().CypherId()), ctx.memory);
-  }
-}
-
-TypedValue ToString(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, String, Number, Bool>>("toString", args, nargs);
-  const auto &arg = args[0];
-  if (arg.IsNull()) {
-    return TypedValue(ctx.memory);
-  } else if (arg.IsString()) {
-    return TypedValue(arg, ctx.memory);
-  } else if (arg.IsInt()) {
-    // TODO: This is making a pointless copy of std::string, we may want to
-    // use a different conversion to string
-    return TypedValue(std::to_string(arg.ValueInt()), ctx.memory);
-  } else if (arg.IsDouble()) {
-    return TypedValue(std::to_string(arg.ValueDouble()), ctx.memory);
-  } else {
-    return TypedValue(arg.ValueBool() ? "true" : "false", ctx.memory);
-  }
-}
-
-TypedValue Timestamp(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Optional<Or<Date, LocalTime, LocalDateTime, Duration>>>("timestamp", args, nargs);
-  const auto &arg = *args;
-  if (arg.IsDate()) {
-    return TypedValue(arg.ValueDate().MicrosecondsSinceEpoch(), ctx.memory);
-  }
-  if (arg.IsLocalTime()) {
-    return TypedValue(arg.ValueLocalTime().MicrosecondsSinceEpoch(), ctx.memory);
-  }
-  if (arg.IsLocalDateTime()) {
-    return TypedValue(arg.ValueLocalDateTime().MicrosecondsSinceEpoch(), ctx.memory);
-  }
-  if (arg.IsDuration()) {
-    return TypedValue(arg.ValueDuration().microseconds, ctx.memory);
-  }
-  return TypedValue(ctx.timestamp, ctx.memory);
-}
-
-TypedValue Left(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, String>, Or<Null, NonNegativeInteger>>("left", args, nargs);
-  if (args[0].IsNull() || args[1].IsNull()) return TypedValue(ctx.memory);
-  return TypedValue(utils::Substr(args[0].ValueString(), 0, args[1].ValueInt()), ctx.memory);
-}
-
-TypedValue Right(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, String>, Or<Null, NonNegativeInteger>>("right", args, nargs);
-  if (args[0].IsNull() || args[1].IsNull()) return TypedValue(ctx.memory);
-  const auto &str = args[0].ValueString();
-  auto len = args[1].ValueInt();
-  return len <= str.size() ? TypedValue(utils::Substr(str, str.size() - len, len), ctx.memory)
-                           : TypedValue(str, ctx.memory);
-}
-
-TypedValue CallStringFunction(const TypedValue *args, int64_t nargs, utils::MemoryResource *memory, const char *name,
-                              std::function<TypedValue::TString(const TypedValue::TString &)> fun) {
-  FType<Or<Null, String>>(name, args, nargs);
-  if (args[0].IsNull()) return TypedValue(memory);
-  return TypedValue(fun(args[0].ValueString()), memory);
-}
-
-TypedValue LTrim(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  return CallStringFunction(args, nargs, ctx.memory, "lTrim",
-                            [&](const auto &str) { return TypedValue::TString(utils::LTrim(str), ctx.memory); });
-}
-
-TypedValue RTrim(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  return CallStringFunction(args, nargs, ctx.memory, "rTrim",
-                            [&](const auto &str) { return TypedValue::TString(utils::RTrim(str), ctx.memory); });
-}
-
-TypedValue Trim(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  return CallStringFunction(args, nargs, ctx.memory, "trim",
-                            [&](const auto &str) { return TypedValue::TString(utils::Trim(str), ctx.memory); });
-}
-
-TypedValue Reverse(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  return CallStringFunction(args, nargs, ctx.memory, "reverse",
-                            [&](const auto &str) { return utils::Reversed(str, ctx.memory); });
-}
-
-TypedValue ToLower(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  return CallStringFunction(args, nargs, ctx.memory, "toLower", [&](const auto &str) {
-    TypedValue::TString res(ctx.memory);
-    utils::ToLowerCase(&res, str);
-    return res;
-  });
-}
-
-TypedValue ToUpper(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  return CallStringFunction(args, nargs, ctx.memory, "toUpper", [&](const auto &str) {
-    TypedValue::TString res(ctx.memory);
-    utils::ToUpperCase(&res, str);
-    return res;
-  });
-}
-
-TypedValue Replace(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, String>, Or<Null, String>, Or<Null, String>>("replace", args, nargs);
-  if (args[0].IsNull() || args[1].IsNull() || args[2].IsNull()) {
-    return TypedValue(ctx.memory);
-  }
-  TypedValue::TString replaced(ctx.memory);
-  utils::Replace(&replaced, args[0].ValueString(), args[1].ValueString(), args[2].ValueString());
-  return TypedValue(std::move(replaced));
-}
-
-TypedValue Split(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, String>, Or<Null, String>>("split", args, nargs);
-  if (args[0].IsNull() || args[1].IsNull()) {
-    return TypedValue(ctx.memory);
-  }
-  TypedValue::TVector result(ctx.memory);
-  utils::Split(&result, args[0].ValueString(), args[1].ValueString());
-  return TypedValue(std::move(result));
-}
-
-TypedValue Substring(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<Null, String>, NonNegativeInteger, Optional<NonNegativeInteger>>("substring", args, nargs);
-  if (args[0].IsNull()) return TypedValue(ctx.memory);
-  const auto &str = args[0].ValueString();
-  auto start = args[1].ValueInt();
-  if (nargs == 2) return TypedValue(utils::Substr(str, start), ctx.memory);
-  auto len = args[2].ValueInt();
-  return TypedValue(utils::Substr(str, start, len), ctx.memory);
-}
-
-TypedValue ToByteString(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<String>("toByteString", args, nargs);
-  const auto &str = args[0].ValueString();
-  if (str.empty()) return TypedValue("", ctx.memory);
-  if (!utils::StartsWith(str, "0x") && !utils::StartsWith(str, "0X")) {
-    throw QueryRuntimeException("'toByteString' argument must start with '0x'");
-  }
-  const auto &hex_str = utils::Substr(str, 2);
-  auto read_hex = [](const char ch) -> unsigned char {
-    if (ch >= '0' && ch <= '9') return ch - '0';
-    if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
-    if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10;
-    throw QueryRuntimeException("'toByteString' argument has an invalid character '{}'", ch);
-  };
-  utils::pmr::string bytes(ctx.memory);
-  bytes.reserve((1 + hex_str.size()) / 2);
-  size_t i = 0;
-  // Treat odd length hex string as having a leading zero.
-  if (hex_str.size() % 2) bytes.append(1, read_hex(hex_str[i++]));
-  for (; i < hex_str.size(); i += 2) {
-    unsigned char byte = read_hex(hex_str[i]) * 16U + read_hex(hex_str[i + 1]);
-    // MemcpyCast in case we are converting to a signed value, so as to avoid
-    // undefined behaviour.
-    bytes.append(1, utils::MemcpyCast<decltype(bytes)::value_type>(byte));
-  }
-  return TypedValue(std::move(bytes));
-}
-
-TypedValue FromByteString(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<String, Optional<PositiveInteger>>("fromByteString", args, nargs);
-  const auto &bytes = args[0].ValueString();
-  if (bytes.empty()) return TypedValue("", ctx.memory);
-  size_t min_length = bytes.size();
-  if (nargs == 2) min_length = std::max(min_length, static_cast<size_t>(args[1].ValueInt()));
-  utils::pmr::string str(ctx.memory);
-  str.reserve(min_length * 2 + 2);
-  str.append("0x");
-  for (size_t pad = 0; pad < min_length - bytes.size(); ++pad) str.append(2, '0');
-  // Convert the bytes to a character string in hex representation.
-  // Unfortunately, we don't know whether the default `char` is signed or
-  // unsigned, so we have to work around any potential undefined behaviour when
-  // conversions between the 2 occur. That's why this function is more
-  // complicated than it should be.
-  auto to_hex = [](const unsigned char val) -> char {
-    unsigned char ch = val < 10U ? static_cast<unsigned char>('0') + val : static_cast<unsigned char>('a') + val - 10U;
-    return utils::MemcpyCast<char>(ch);
-  };
-  for (unsigned char byte : bytes) {
-    str.append(1, to_hex(byte / 16U));
-    str.append(1, to_hex(byte % 16U));
-  }
-  return TypedValue(std::move(str));
-}
-
-template <typename T>
-concept IsNumberOrInteger = utils::SameAsAnyOf<T, Number, Integer>;
-
-template <IsNumberOrInteger ArgType>
-void MapNumericParameters(auto &parameter_mappings, const auto &input_parameters) {
-  for (const auto &[key, value] : input_parameters) {
-    if (auto it = parameter_mappings.find(key); it != parameter_mappings.end()) {
-      if (value.IsInt()) {
-        *it->second = value.ValueInt();
-      } else if (std::is_same_v<ArgType, Number> && value.IsDouble()) {
-        *it->second = value.ValueDouble();
-      } else {
-        std::string_view error = std::is_same_v<ArgType, Integer> ? "an integer." : "a numeric value.";
-        throw QueryRuntimeException("Invalid value for key '{}'. Expected {}", key, error);
-      }
-    } else {
-      throw QueryRuntimeException("Unknown key '{}'.", key);
-    }
-  }
-}
-
-TypedValue Date(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Optional<Or<String, Map>>>("date", args, nargs);
-  if (nargs == 0) {
-    return TypedValue(utils::LocalDateTime(ctx.timestamp).date, ctx.memory);
-  }
-
-  if (args[0].IsString()) {
-    const auto &[date_parameters, is_extended] = utils::ParseDateParameters(args[0].ValueString());
-    return TypedValue(utils::Date(date_parameters), ctx.memory);
-  }
-
-  utils::DateParameters date_parameters;
-
-  using namespace std::literals;
-  std::unordered_map parameter_mappings = {std::pair{"year"sv, &date_parameters.year},
-                                           std::pair{"month"sv, &date_parameters.month},
-                                           std::pair{"day"sv, &date_parameters.day}};
-
-  MapNumericParameters<Integer>(parameter_mappings, args[0].ValueMap());
-  return TypedValue(utils::Date(date_parameters), ctx.memory);
-}
-
-TypedValue LocalTime(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Optional<Or<String, Map>>>("localtime", args, nargs);
-
-  if (nargs == 0) {
-    return TypedValue(utils::LocalDateTime(ctx.timestamp).local_time, ctx.memory);
-  }
-
-  if (args[0].IsString()) {
-    const auto &[local_time_parameters, is_extended] = utils::ParseLocalTimeParameters(args[0].ValueString());
-    return TypedValue(utils::LocalTime(local_time_parameters), ctx.memory);
-  }
-
-  utils::LocalTimeParameters local_time_parameters;
-
-  using namespace std::literals;
-  std::unordered_map parameter_mappings{
-      std::pair{"hour"sv, &local_time_parameters.hour},
-      std::pair{"minute"sv, &local_time_parameters.minute},
-      std::pair{"second"sv, &local_time_parameters.second},
-      std::pair{"millisecond"sv, &local_time_parameters.millisecond},
-      std::pair{"microsecond"sv, &local_time_parameters.microsecond},
-  };
-
-  MapNumericParameters<Integer>(parameter_mappings, args[0].ValueMap());
-  return TypedValue(utils::LocalTime(local_time_parameters), ctx.memory);
-}
-
-TypedValue LocalDateTime(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Optional<Or<String, Map>>>("localdatetime", args, nargs);
-
-  if (nargs == 0) {
-    return TypedValue(utils::LocalDateTime(ctx.timestamp), ctx.memory);
-  }
-
-  if (args[0].IsString()) {
-    const auto &[date_parameters, local_time_parameters] = ParseLocalDateTimeParameters(args[0].ValueString());
-    return TypedValue(utils::LocalDateTime(date_parameters, local_time_parameters), ctx.memory);
-  }
-
-  utils::DateParameters date_parameters;
-  utils::LocalTimeParameters local_time_parameters;
-  using namespace std::literals;
-  std::unordered_map parameter_mappings{
-      std::pair{"year"sv, &date_parameters.year},
-      std::pair{"month"sv, &date_parameters.month},
-      std::pair{"day"sv, &date_parameters.day},
-      std::pair{"hour"sv, &local_time_parameters.hour},
-      std::pair{"minute"sv, &local_time_parameters.minute},
-      std::pair{"second"sv, &local_time_parameters.second},
-      std::pair{"millisecond"sv, &local_time_parameters.millisecond},
-      std::pair{"microsecond"sv, &local_time_parameters.microsecond},
-  };
-
-  MapNumericParameters<Integer>(parameter_mappings, args[0].ValueMap());
-  return TypedValue(utils::LocalDateTime(date_parameters, local_time_parameters), ctx.memory);
-}
-
-TypedValue Duration(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
-  FType<Or<String, Map>>("duration", args, nargs);
-
-  if (args[0].IsString()) {
-    return TypedValue(utils::Duration(ParseDurationParameters(args[0].ValueString())), ctx.memory);
-  }
-
-  utils::DurationParameters duration_parameters;
-  using namespace std::literals;
-  std::unordered_map parameter_mappings{std::pair{"day"sv, &duration_parameters.day},
-                                        std::pair{"hour"sv, &duration_parameters.hour},
-                                        std::pair{"minute"sv, &duration_parameters.minute},
-                                        std::pair{"second"sv, &duration_parameters.second},
-                                        std::pair{"millisecond"sv, &duration_parameters.millisecond},
-                                        std::pair{"microsecond"sv, &duration_parameters.microsecond}};
-  MapNumericParameters<Number>(parameter_mappings, args[0].ValueMap());
-  return TypedValue(utils::Duration(duration_parameters), ctx.memory);
-}
-
-}  // namespace
-
-std::function<TypedValue(const TypedValue *, int64_t, const FunctionContext &ctx)> NameToFunction(
-    const std::string &function_name) {
-  // Scalar functions
-  if (function_name == "ENDNODE") return EndNode;
-  if (function_name == "HEAD") return Head;
-  if (function_name == kId) return Id;
-  if (function_name == "LAST") return Last;
-  if (function_name == "PROPERTIES") return Properties;
-  if (function_name == "SIZE") return Size;
-  if (function_name == "STARTNODE") return StartNode;
-  if (function_name == "TIMESTAMP") return Timestamp;
-  if (function_name == "TOBOOLEAN") return ToBoolean;
-  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 == "RANGE") return Range;
-  if (function_name == "TAIL") return Tail;
-  if (function_name == "UNIFORMSAMPLE") return UniformSample;
-
-  // Mathematical functions - numeric
-  if (function_name == "ABS") return Abs;
-  if (function_name == "CEIL") return Ceil;
-  if (function_name == "FLOOR") return Floor;
-  if (function_name == "RAND") return Rand;
-  if (function_name == "ROUND") return Round;
-  if (function_name == "SIGN") return Sign;
-
-  // Mathematical functions - logarithmic
-  if (function_name == "E") return E;
-  if (function_name == "EXP") return Exp;
-  if (function_name == "LOG") return Log;
-  if (function_name == "LOG10") return Log10;
-  if (function_name == "SQRT") return Sqrt;
-
-  // Mathematical functions - trigonometric
-  if (function_name == "ACOS") return Acos;
-  if (function_name == "ASIN") return Asin;
-  if (function_name == "ATAN") return Atan;
-  if (function_name == "ATAN2") return Atan2;
-  if (function_name == "COS") return Cos;
-  if (function_name == "PI") return Pi;
-  if (function_name == "SIN") return Sin;
-  if (function_name == "TAN") return Tan;
-
-  // String functions
-  if (function_name == kContains) return Contains;
-  if (function_name == kEndsWith) return EndsWith;
-  if (function_name == "LEFT") return Left;
-  if (function_name == "LTRIM") return LTrim;
-  if (function_name == "REPLACE") return Replace;
-  if (function_name == "REVERSE") return Reverse;
-  if (function_name == "RIGHT") return Right;
-  if (function_name == "RTRIM") return RTrim;
-  if (function_name == "SPLIT") return Split;
-  if (function_name == kStartsWith) return StartsWith;
-  if (function_name == "SUBSTRING") return Substring;
-  if (function_name == "TOLOWER") return ToLower;
-  // TODO(kostasrim) fix function lookup here
-  if (function_name == "TOSTRING") return ToString;
-  if (function_name == "TOUPPER") return ToUpper;
-  if (function_name == "TRIM") return Trim;
-
-  // Memgraph specific functions
-  if (function_name == "ASSERT") return Assert;
-  if (function_name == "COUNTER") return Counter;
-  if (function_name == "TOBYTESTRING") return ToByteString;
-  if (function_name == "FROMBYTESTRING") return FromByteString;
-
-  // Functions for temporal types
-  if (function_name == "DATE") return Date;
-  if (function_name == "LOCALTIME") return LocalTime;
-  if (function_name == "LOCALDATETIME") return LocalDateTime;
-  if (function_name == "DURATION") return Duration;
-
-  return nullptr;
-}
-
-}  // namespace memgraph::query::v2
diff --git a/src/query/v2/plan/preprocess.cpp b/src/query/v2/plan/preprocess.cpp
index 1037f3fe6..765066079 100644
--- a/src/query/v2/plan/preprocess.cpp
+++ b/src/query/v2/plan/preprocess.cpp
@@ -398,7 +398,7 @@ void Filters::AnalyzeAndStoreFilter(Expression *expr, const SymbolTable &symbol_
   auto add_id_equal = [&](auto *maybe_id_fun, auto *val_expr) -> bool {
     auto *id_fun = utils::Downcast<Function>(maybe_id_fun);
     if (!id_fun) return false;
-    if (id_fun->function_name_ != kId) return false;
+    if (id_fun->function_name_ != functions::kId) return false;
     if (id_fun->arguments_.size() != 1U) return false;
     auto *ident = utils::Downcast<Identifier>(id_fun->arguments_.front());
     if (!ident) return false;
diff --git a/src/query/v2/shard_request_manager.hpp b/src/query/v2/shard_request_manager.hpp
index 68a10f384..581dfbcab 100644
--- a/src/query/v2/shard_request_manager.hpp
+++ b/src/query/v2/shard_request_manager.hpp
@@ -125,9 +125,12 @@ class ShardRequestManagerInterface {
   virtual storage::v3::EdgeTypeId NameToEdgeType(const std::string &name) const = 0;
   virtual storage::v3::PropertyId NameToProperty(const std::string &name) const = 0;
   virtual storage::v3::LabelId NameToLabel(const std::string &name) const = 0;
-  virtual const std::string &PropertyToName(storage::v3::PropertyId prop) const = 0;
-  virtual const std::string &LabelToName(storage::v3::LabelId label) const = 0;
-  virtual const std::string &EdgeTypeToName(storage::v3::EdgeTypeId type) const = 0;
+  virtual const std::string &PropertyToName(memgraph::storage::v3::PropertyId prop) const = 0;
+  virtual const std::string &LabelToName(memgraph::storage::v3::LabelId label) const = 0;
+  virtual const std::string &EdgeTypeToName(memgraph::storage::v3::EdgeTypeId type) const = 0;
+  virtual std::optional<storage::v3::PropertyId> MaybeNameToProperty(const std::string &name) const = 0;
+  virtual std::optional<storage::v3::EdgeTypeId> MaybeNameToEdgeType(const std::string &name) const = 0;
+  virtual std::optional<storage::v3::LabelId> MaybeNameToLabel(const std::string &name) const = 0;
   virtual bool IsPrimaryLabel(storage::v3::LabelId label) const = 0;
   virtual bool IsPrimaryKey(storage::v3::LabelId primary_label, storage::v3::PropertyId property) const = 0;
 };
@@ -352,6 +355,18 @@ class ShardRequestManager : public ShardRequestManagerInterface {
     return result_rows;
   }
 
+  std::optional<storage::v3::PropertyId> MaybeNameToProperty(const std::string &name) const override {
+    return shards_map_.GetPropertyId(name);
+  }
+
+  std::optional<storage::v3::EdgeTypeId> MaybeNameToEdgeType(const std::string &name) const override {
+    return shards_map_.GetEdgeTypeId(name);
+  }
+
+  std::optional<storage::v3::LabelId> MaybeNameToLabel(const std::string &name) const override {
+    return shards_map_.GetLabelId(name);
+  }
+
  private:
   enum class PaginatedResponseState { Pending, PartiallyFinished };
 
diff --git a/src/storage/v3/CMakeLists.txt b/src/storage/v3/CMakeLists.txt
index 5a824534b..2b3a7c861 100644
--- a/src/storage/v3/CMakeLists.txt
+++ b/src/storage/v3/CMakeLists.txt
@@ -31,4 +31,4 @@ target_link_libraries(mg-storage-v3 Threads::Threads mg-utils gflags)
 target_include_directories(mg-storage-v3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/bindings)
 
 add_dependencies(mg-storage-v3 generate_lcp_storage)
-target_link_libraries(mg-storage-v3 mg-slk mg-expr mg-io)
+target_link_libraries(mg-storage-v3 mg-slk mg-expr mg-io mg-functions)
diff --git a/src/storage/v3/bindings/ast/ast.lcp b/src/storage/v3/bindings/ast/ast.lcp
index 3be1830ae..8a3484a58 100644
--- a/src/storage/v3/bindings/ast/ast.lcp
+++ b/src/storage/v3/bindings/ast/ast.lcp
@@ -24,6 +24,8 @@
 #include "storage/v3/conversions.hpp"
 #include "storage/v3/path.hpp"
 #include "utils/typeinfo.hpp"
+#include "utils/exceptions.hpp"
+#include "functions/awesome_memgraph_functions.hpp"
 
 cpp<#
 
@@ -178,14 +180,6 @@ cpp<#
   (:serialize (:slk :load-args '((storage "storage::v3::AstStorage *")))))
 
 #>cpp
-struct FunctionContext {
-  DbAccessor *db_accessor;
-  utils::MemoryResource *memory;
-  int64_t timestamp;
-  std::unordered_map<std::string, int64_t> *counters;
-  View view;
-};
-
 inline bool operator==(const LabelIx &a, const LabelIx &b) {
   return a.ix == b.ix && a.name == b.name;
 }
@@ -845,16 +839,24 @@ cpp<#
               :slk-load (slk-load-ast-vector "Expression"))
    (function-name "std::string" :scope :public)
    (function "std::function<TypedValue(const TypedValue *, int64_t,
-                                       const FunctionContext &)>"
+                                       const functions::FunctionContext<memgraph::storage::v3::DbAccessor> &)>"
              :scope :public
              :dont-save t
              :clone :copy
              :slk-load (lambda (member)
                         #>cpp
+                        self->${member} = functions::NameToFunction<memgraph::storage::v3::TypedValue,
+                                            functions::FunctionContext<memgraph::storage::v3::DbAccessor>,
+                                            functions::StorageEngineTag, Conv>(self->function_name_);
                         cpp<#)))
   (:public
     #>cpp
     Function() = default;
+    using Conv = decltype(PropertyToTypedValueFunctor<TypedValue>);
+    class SemanticException : public memgraph::utils::BasicException {
+      using utils::BasicException::BasicException;
+    };
+
 
     DEFVISITABLE(ExpressionVisitor<TypedValue>);
     DEFVISITABLE(ExpressionVisitor<void>);
@@ -872,7 +874,13 @@ cpp<#
     Function(const std::string &function_name,
              const std::vector<Expression *> &arguments)
         : arguments_(arguments),
-          function_name_(function_name) {
+          function_name_(function_name),
+          function_(functions::NameToFunction<memgraph::storage::v3::TypedValue,
+                                            functions::FunctionContext<memgraph::storage::v3::DbAccessor>,
+                                            functions::StorageEngineTag, Conv>(function_name_)) {
+      if (!function_) {
+        throw SemanticException("Function '{}' doesn't exist.", function_name);
+      }
     }
     cpp<#)
   (:private
diff --git a/src/storage/v3/bindings/db_accessor.hpp b/src/storage/v3/bindings/db_accessor.hpp
index 1a5e846fa..6392ed18f 100644
--- a/src/storage/v3/bindings/db_accessor.hpp
+++ b/src/storage/v3/bindings/db_accessor.hpp
@@ -88,7 +88,7 @@ class DbAccessor final {
   }
 
   storage::v3::ShardResult<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge) {
-    auto res = accessor_->DeleteEdge(edge->FromVertex(), edge->ToVertex(), edge->Gid());
+    auto res = accessor_->DeleteEdge(edge->From(), edge->To(), edge->Gid());
     if (res.HasError()) {
       return res.GetError();
     }
diff --git a/src/storage/v3/conversions.hpp b/src/storage/v3/conversions.hpp
index 3ff804b6a..dfb190ac1 100644
--- a/src/storage/v3/conversions.hpp
+++ b/src/storage/v3/conversions.hpp
@@ -69,6 +69,10 @@ TTypedValue PropertyToTypedValue(const PropertyValue &value) {
   LOG_FATAL("Unsupported type");
 }
 
+template <typename TypedValueT>
+inline const auto PropertyToTypedValueFunctor =
+    [](const PropertyValue &value) { return PropertyToTypedValue<TypedValueT>(value); };
+
 template <typename TTypedValue>
 TTypedValue PropertyToTypedValue(const PropertyValue &value, utils::MemoryResource *mem) {
   switch (value.type()) {
diff --git a/src/storage/v3/edge_accessor.cpp b/src/storage/v3/edge_accessor.cpp
index 95dd6875a..e591de7eb 100644
--- a/src/storage/v3/edge_accessor.cpp
+++ b/src/storage/v3/edge_accessor.cpp
@@ -51,9 +51,9 @@ bool EdgeAccessor::IsVisible(const View view) const {
   return exists && (for_deleted_ || !deleted);
 }
 
-const VertexId &EdgeAccessor::FromVertex() const { return from_vertex_; }
+const VertexId &EdgeAccessor::From() const { return from_vertex_; }
 
-const VertexId &EdgeAccessor::ToVertex() const { return to_vertex_; }
+const VertexId &EdgeAccessor::To() const { return to_vertex_; }
 
 ShardResult<PropertyValue> EdgeAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
   utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
@@ -180,4 +180,7 @@ ShardResult<std::map<PropertyId, PropertyValue>> EdgeAccessor::Properties(View v
   return std::move(properties);
 }
 
+// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
+size_t EdgeAccessor::CypherId() const { return Gid().AsUint(); }
+
 }  // namespace memgraph::storage::v3
diff --git a/src/storage/v3/edge_accessor.hpp b/src/storage/v3/edge_accessor.hpp
index 105d28fa4..bc143c1e3 100644
--- a/src/storage/v3/edge_accessor.hpp
+++ b/src/storage/v3/edge_accessor.hpp
@@ -48,9 +48,9 @@ class EdgeAccessor final {
   /// @return true if the object is visible from the current transaction
   bool IsVisible(View view) const;
 
-  const VertexId &FromVertex() const;
+  const VertexId &From() const;
 
-  const VertexId &ToVertex() const;
+  const VertexId &To() const;
 
   EdgeTypeId EdgeType() const { return edge_type_; }
 
@@ -84,6 +84,9 @@ class EdgeAccessor final {
   }
   bool operator!=(const EdgeAccessor &other) const noexcept { return !(*this == other); }
 
+  // Dummy function
+  size_t CypherId() const;
+
  private:
   EdgeRef edge_;
   EdgeTypeId edge_type_;
diff --git a/src/storage/v3/expr.cpp b/src/storage/v3/expr.cpp
index 8062a2662..36c0ff8e3 100644
--- a/src/storage/v3/expr.cpp
+++ b/src/storage/v3/expr.cpp
@@ -52,13 +52,12 @@ msgs::Value ConstructValueEdge(const EdgeAccessor &acc, View view) {
   msgs::EdgeType type = {.id = acc.EdgeType()};
   msgs::EdgeId gid = {.gid = acc.Gid().AsUint()};
 
-  msgs::Label src_prim_label = {.id = acc.FromVertex().primary_label};
+  msgs::Label src_prim_label = {.id = acc.From().primary_label};
   memgraph::msgs::VertexId src_vertex =
-      std::make_pair(src_prim_label, conversions::ConvertValueVector(acc.FromVertex().primary_key));
+      std::make_pair(src_prim_label, conversions::ConvertValueVector(acc.From().primary_key));
 
-  msgs::Label dst_prim_label = {.id = acc.ToVertex().primary_label};
-  msgs::VertexId dst_vertex =
-      std::make_pair(dst_prim_label, conversions::ConvertValueVector(acc.ToVertex().primary_key));
+  msgs::Label dst_prim_label = {.id = acc.To().primary_label};
+  msgs::VertexId dst_vertex = std::make_pair(dst_prim_label, conversions::ConvertValueVector(acc.To().primary_key));
 
   auto properties = acc.Properties(view);
 
diff --git a/src/storage/v3/request_helper.cpp b/src/storage/v3/request_helper.cpp
index 481741f60..3b0c18326 100644
--- a/src/storage/v3/request_helper.cpp
+++ b/src/storage/v3/request_helper.cpp
@@ -257,7 +257,7 @@ EdgeUniquenessFunction InitializeEdgeUniquenessFunction(bool only_unique_neighbo
         case msgs::EdgeDirection::OUT: {
           is_edge_unique = [](std::set<const storage::v3::VertexId *, VertexIdCmpr> &other_vertex_set,
                               const storage::v3::EdgeAccessor &edge_acc) {
-            auto [it, insertion_happened] = other_vertex_set.insert(&edge_acc.ToVertex());
+            auto [it, insertion_happened] = other_vertex_set.insert(&edge_acc.To());
             return insertion_happened;
           };
           break;
@@ -265,7 +265,7 @@ EdgeUniquenessFunction InitializeEdgeUniquenessFunction(bool only_unique_neighbo
         case msgs::EdgeDirection::IN: {
           is_edge_unique = [](std::set<const storage::v3::VertexId *, VertexIdCmpr> &other_vertex_set,
                               const storage::v3::EdgeAccessor &edge_acc) {
-            auto [it, insertion_happened] = other_vertex_set.insert(&edge_acc.FromVertex());
+            auto [it, insertion_happened] = other_vertex_set.insert(&edge_acc.From());
             return insertion_happened;
           };
           break;
@@ -311,8 +311,8 @@ EdgeFiller InitializeEdgeFillerFunction(const msgs::ExpandOneRequest &req) {
         value_properties.insert(std::make_pair(prop_key, FromPropertyValueToValue(std::move(prop_val))));
       }
       using EdgeWithAllProperties = msgs::ExpandOneResultRow::EdgeWithAllProperties;
-      EdgeWithAllProperties edges{ToMsgsVertexId(edge.FromVertex()), msgs::EdgeType{edge.EdgeType()},
-                                  edge.Gid().AsUint(), std::move(value_properties)};
+      EdgeWithAllProperties edges{ToMsgsVertexId(edge.From()), msgs::EdgeType{edge.EdgeType()}, edge.Gid().AsUint(),
+                                  std::move(value_properties)};
       if (is_in_edge) {
         result_row.in_edges_with_all_properties.push_back(std::move(edges));
       } else {
@@ -336,7 +336,7 @@ EdgeFiller InitializeEdgeFillerFunction(const msgs::ExpandOneRequest &req) {
         value_properties.emplace_back(FromPropertyValueToValue(std::move(property_result.GetValue())));
       }
       using EdgeWithSpecificProperties = msgs::ExpandOneResultRow::EdgeWithSpecificProperties;
-      EdgeWithSpecificProperties edges{ToMsgsVertexId(edge.FromVertex()), msgs::EdgeType{edge.EdgeType()},
+      EdgeWithSpecificProperties edges{ToMsgsVertexId(edge.From()), msgs::EdgeType{edge.EdgeType()},
                                        edge.Gid().AsUint(), std::move(value_properties)};
       if (is_in_edge) {
         result_row.in_edges_with_specific_properties.push_back(std::move(edges));
diff --git a/src/storage/v3/shard.cpp b/src/storage/v3/shard.cpp
index 79093449c..5dd8e7256 100644
--- a/src/storage/v3/shard.cpp
+++ b/src/storage/v3/shard.cpp
@@ -442,7 +442,7 @@ ShardResult<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>>
   for (const auto &item : in_edges) {
     auto [edge_type, from_vertex, edge] = item;
     EdgeAccessor e(edge, edge_type, from_vertex, vertex_id, transaction_, &shard_->indices_, config_);
-    auto ret = DeleteEdge(e.FromVertex(), e.ToVertex(), e.Gid());
+    auto ret = DeleteEdge(e.From(), e.To(), e.Gid());
     if (ret.HasError()) {
       MG_ASSERT(ret.GetError() == common::ErrorCode::SERIALIZATION_ERROR, "Invalid database state!");
       return ret.GetError();
@@ -455,7 +455,7 @@ ShardResult<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>>
   for (const auto &item : out_edges) {
     auto [edge_type, to_vertex, edge] = item;
     EdgeAccessor e(edge, edge_type, vertex_id, to_vertex, transaction_, &shard_->indices_, config_);
-    auto ret = DeleteEdge(e.FromVertex(), e.ToVertex(), e.Gid());
+    auto ret = DeleteEdge(e.From(), e.To(), e.Gid());
     if (ret.HasError()) {
       MG_ASSERT(ret.GetError() == common::ErrorCode::SERIALIZATION_ERROR, "Invalid database state!");
       return ret.GetError();
diff --git a/tests/e2e/distributed_queries/CMakeLists.txt b/tests/e2e/distributed_queries/CMakeLists.txt
index 455e6ad45..5a7a8d81b 100644
--- a/tests/e2e/distributed_queries/CMakeLists.txt
+++ b/tests/e2e/distributed_queries/CMakeLists.txt
@@ -9,3 +9,4 @@ distributed_queries_e2e_python_files(order_by_and_limit.py)
 distributed_queries_e2e_python_files(distinct.py)
 distributed_queries_e2e_python_files(optional_match.py)
 distributed_queries_e2e_python_files(common.py)
+distributed_queries_e2e_python_files(awesome_memgraph_functions.py)
diff --git a/tests/e2e/distributed_queries/awesome_memgraph_functions.py b/tests/e2e/distributed_queries/awesome_memgraph_functions.py
new file mode 100644
index 000000000..0bdaa07a4
--- /dev/null
+++ b/tests/e2e/distributed_queries/awesome_memgraph_functions.py
@@ -0,0 +1,93 @@
+# Copyright 2022 Memgraph Ltd.
+#
+# Use of this software is governed by the Business Source License
+# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+# License, and you may not use this file except in compliance with the Business Source License.
+#
+# As of the Change Date specified in that file, in accordance with
+# the Business Source License, use of this software will be governed
+# by the Apache License, Version 2.0, included in the file
+# licenses/APL.txt.
+
+import sys
+
+import mgclient
+import pytest
+
+from common import (
+    connection,
+    execute_and_fetch_all,
+    has_n_result_row,
+    wait_for_shard_manager_to_initialize,
+)
+
+
+def test_awesome_memgraph_functions(connection):
+    wait_for_shard_manager_to_initialize()
+    cursor = connection.cursor()
+
+    assert has_n_result_row(cursor, "CREATE (n :label {property:1})", 0)
+    assert has_n_result_row(cursor, "CREATE (n :label {property:2})", 0)
+    assert has_n_result_row(cursor, "CREATE (n :label {property:3})", 0)
+    assert has_n_result_row(cursor, "CREATE (n :label {property:4})", 0)
+    assert has_n_result_row(cursor, "CREATE (n :label {property:10})", 0)
+
+    results = execute_and_fetch_all(cursor, "MATCH (n) WITH COLLECT(n) as nn RETURN SIZE(nn)")
+    assert len(results) == 1
+    assert results[0][0] == 5
+
+    results = execute_and_fetch_all(cursor, "MATCH (n) WITH COLLECT(n.property) as nn RETURN ALL(i IN nn WHERE i > 0)")
+    assert len(results) == 1
+    assert results[0][0] == True
+
+    results = execute_and_fetch_all(cursor, """RETURN CONTAINS("Pineapple", "P")""")
+    assert len(results) == 1
+    assert results[0][0] == True
+
+    results = execute_and_fetch_all(cursor, """RETURN ENDSWITH("Pineapple", "e")""")
+    assert len(results) == 1
+    assert results[0][0] == True
+
+    results = execute_and_fetch_all(cursor, """RETURN LEFT("Pineapple", 1)""")
+    assert len(results) == 1
+    assert results[0][0] == "P"
+
+    results = execute_and_fetch_all(cursor, """RETURN RIGHT("Pineapple", 1)""")
+    assert len(results) == 1
+    assert results[0][0] == "e"
+
+    results = execute_and_fetch_all(cursor, """RETURN REVERSE("Apple")""")
+    assert len(results) == 1
+    assert results[0][0] == "elppA"
+
+    results = execute_and_fetch_all(cursor, """RETURN REPLACE("Apple", "A", "a")""")
+    assert len(results) == 1
+    assert results[0][0] == "apple"
+
+    results = execute_and_fetch_all(cursor, """RETURN TOLOWER("Apple")""")
+    assert len(results) == 1
+    assert results[0][0] == "apple"
+
+    results = execute_and_fetch_all(cursor, """RETURN TOUPPER("Apple")""")
+    assert len(results) == 1
+    assert results[0][0] == "APPLE"
+
+    results = execute_and_fetch_all(cursor, """RETURN TRIM("   Apple")""")
+    assert len(results) == 1
+    assert results[0][0] == "Apple"
+
+    results = execute_and_fetch_all(cursor, """RETURN SPLIT("Apple.Apple", ".")""")
+    assert len(results) == 1
+    assert results[0][0] == ["Apple", "Apple"]
+
+    results = execute_and_fetch_all(cursor, """RETURN LOG10(100)""")
+    assert len(results) == 1
+    assert results[0][0] == 2
+
+    results = execute_and_fetch_all(cursor, """RETURN SQRT(4)""")
+    assert len(results) == 1
+    assert results[0][0] == 2
+
+
+if __name__ == "__main__":
+    sys.exit(pytest.main([__file__, "-rA"]))
diff --git a/tests/e2e/distributed_queries/workloads.yaml b/tests/e2e/distributed_queries/workloads.yaml
index 741d6d939..eacc3e713 100644
--- a/tests/e2e/distributed_queries/workloads.yaml
+++ b/tests/e2e/distributed_queries/workloads.yaml
@@ -36,3 +36,8 @@ workloads:
     binary: "tests/e2e/pytest_runner.sh"
     args: ["distributed_queries/optional_match.py"]
     <<: *template_cluster
+
+  - name: "Awesome memgraph functions"
+    binary: "tests/e2e/pytest_runner.sh"
+    args: ["distributed_queries/awesome_memgraph_functions.py"]
+    <<: *template_cluster
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
index b3c8ee06e..188c6c1b0 100644
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -406,3 +406,7 @@ target_link_libraries(${test_prefix}coordinator_shard_map mg-coordinator)
 # Tests for many shards, many creates, scan
 add_unit_test(high_density_shard_create_scan.cpp)
 target_link_libraries(${test_prefix}high_density_shard_create_scan mg-io mg-coordinator mg-storage-v3 mg-query-v2)
+
+# Tests for awesome_memgraph_functions
+add_unit_test(query_v2_expression_evaluator.cpp)
+target_link_libraries(${test_prefix}query_v2_expression_evaluator mg-query-v2)
diff --git a/tests/unit/query_v2_expression_evaluator.cpp b/tests/unit/query_v2_expression_evaluator.cpp
new file mode 100644
index 000000000..1fb4cf6dd
--- /dev/null
+++ b/tests/unit/query_v2_expression_evaluator.cpp
@@ -0,0 +1,1729 @@
+// Copyright 2022 Memgraph Ltd.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#include <chrono>
+#include <cmath>
+#include <iterator>
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "coordinator/shard_map.hpp"
+#include "functions/awesome_memgraph_functions.hpp"
+#include "parser/opencypher/parser.hpp"
+#include "query/v2/accessors.hpp"
+#include "query/v2/bindings/eval.hpp"
+#include "query/v2/bindings/frame.hpp"
+#include "query/v2/context.hpp"
+#include "query/v2/frontend/ast/ast.hpp"
+#include "query/v2/requests.hpp"
+#include "query/v2/shard_request_manager.hpp"
+#include "query_v2_query_common.hpp"
+#include "storage/v3/property_value.hpp"
+#include "storage/v3/shard.hpp"
+#include "utils/exceptions.hpp"
+#include "utils/string.hpp"
+
+using memgraph::query::v2::test_common::ToIntList;
+using testing::ElementsAre;
+using testing::UnorderedElementsAre;
+
+using memgraph::io::Time;
+using memgraph::io::TimedOut;
+using memgraph::io::simulator::Simulator;
+using memgraph::io::simulator::SimulatorConfig;
+using memgraph::io::simulator::SimulatorTransport;
+using memgraph::msgs::CreateExpandRequest;
+using memgraph::msgs::CreateExpandResponse;
+using memgraph::msgs::CreateVerticesRequest;
+using memgraph::msgs::CreateVerticesResponse;
+using memgraph::msgs::ExpandOneRequest;
+using memgraph::msgs::ExpandOneResponse;
+using memgraph::msgs::ExpandOneResultRow;
+using memgraph::msgs::NewExpand;
+using memgraph::msgs::NewVertex;
+using memgraph::msgs::ScanVerticesRequest;
+using memgraph::msgs::ScanVerticesResponse;
+using ShardMap = memgraph::coordinator::ShardMap;
+using LabelId = memgraph::storage::v3::LabelId;
+using PropertyId = memgraph::storage::v3::PropertyId;
+using memgraph::coordinator::Shards;
+using CompoundKey = memgraph::coordinator::PrimaryKey;
+using memgraph::expr::ExpressionRuntimeException;
+using memgraph::functions::FunctionRuntimeException;
+
+namespace memgraph::query::v2::tests {
+
+class MockedShardRequestManager : public ShardRequestManagerInterface {
+ public:
+  using VertexAccessor = accessors::VertexAccessor;
+  explicit MockedShardRequestManager(ShardMap shard_map) : shards_map_(std::move(shard_map)) { SetUpNameIdMappers(); }
+  memgraph::storage::v3::EdgeTypeId NameToEdgeType(const std::string &name) const override {
+    return shards_map_.GetEdgeTypeId(name).value();
+  }
+
+  memgraph::storage::v3::PropertyId NameToProperty(const std::string &name) const override {
+    return shards_map_.GetPropertyId(name).value();
+  }
+
+  memgraph::storage::v3::LabelId NameToLabel(const std::string &name) const override {
+    return shards_map_.GetLabelId(name).value();
+  }
+  void StartTransaction() override {}
+  void Commit() override {}
+  std::vector<VertexAccessor> Request(ExecutionState<memgraph::msgs::ScanVerticesRequest> &state) override {
+    return {};
+  }
+
+  std::vector<CreateVerticesResponse> Request(ExecutionState<CreateVerticesRequest> &state,
+                                              std::vector<memgraph::msgs::NewVertex> new_vertices) override {
+    return {};
+  }
+
+  std::vector<ExpandOneResultRow> Request(ExecutionState<ExpandOneRequest> &state, ExpandOneRequest request) override {
+    return {};
+  }
+
+  std::vector<CreateExpandResponse> Request(ExecutionState<CreateExpandRequest> &state,
+                                            std::vector<NewExpand> new_edges) override {
+    return {};
+  }
+
+  const std::string &PropertyToName(memgraph::storage::v3::PropertyId id) const override {
+    return properties_.IdToName(id.AsUint());
+  }
+
+  const std::string &LabelToName(memgraph::storage::v3::LabelId id) const override {
+    return labels_.IdToName(id.AsUint());
+  }
+
+  const std::string &EdgeTypeToName(memgraph::storage::v3::EdgeTypeId id) const override {
+    return edge_types_.IdToName(id.AsUint());
+  }
+
+  std::optional<storage::v3::PropertyId> MaybeNameToProperty(const std::string &name) const override {
+    return shards_map_.GetPropertyId(name);
+  }
+
+  std::optional<storage::v3::EdgeTypeId> MaybeNameToEdgeType(const std::string &name) const override {
+    return shards_map_.GetEdgeTypeId(name);
+  }
+
+  std::optional<storage::v3::LabelId> MaybeNameToLabel(const std::string &name) const override {
+    return shards_map_.GetLabelId(name);
+  }
+
+  bool IsPrimaryLabel(LabelId label) const override { return true; }
+
+  bool IsPrimaryKey(LabelId primary_label, PropertyId property) const override { return true; }
+
+ private:
+  void SetUpNameIdMappers() {
+    std::unordered_map<uint64_t, std::string> id_to_name;
+    for (const auto &[name, id] : shards_map_.labels) {
+      id_to_name.emplace(id.AsUint(), name);
+    }
+    labels_.StoreMapping(std::move(id_to_name));
+    id_to_name.clear();
+    for (const auto &[name, id] : shards_map_.properties) {
+      id_to_name.emplace(id.AsUint(), name);
+    }
+    properties_.StoreMapping(std::move(id_to_name));
+    id_to_name.clear();
+    for (const auto &[name, id] : shards_map_.edge_types) {
+      id_to_name.emplace(id.AsUint(), name);
+    }
+    edge_types_.StoreMapping(std::move(id_to_name));
+  }
+
+  ShardMap shards_map_;
+  memgraph::storage::v3::NameIdMapper properties_;
+  memgraph::storage::v3::NameIdMapper edge_types_;
+  memgraph::storage::v3::NameIdMapper labels_;
+};
+
+ShardMap CreateDummyShardmap() {
+  static const std::string label_name = std::string("label1");
+  ShardMap sm;
+
+  // register new properties
+  const std::vector<std::string> property_names = {"prop", "property_2", "age", "height", "a", "b", "c"};
+  const auto properties = sm.AllocatePropertyIds(property_names);
+  const auto property_id_1 = properties.at("prop");
+  const auto property_id_2 = properties.at("property_2");
+  const auto property_id_3 = properties.at("age");
+  const auto property_id_4 = properties.at("height");
+  const auto property_id_5 = properties.at("a");
+  const auto property_id_6 = properties.at("b");
+  const auto property_id_7 = properties.at("c");
+  const auto type_1 = memgraph::common::SchemaType::INT;
+
+  using SchemaProperty = memgraph::coordinator::SchemaProperty;
+  // register new label space
+  std::vector<SchemaProperty> schema = {
+      SchemaProperty{.property_id = property_id_1, .type = type_1},
+      SchemaProperty{.property_id = property_id_2, .type = type_1},
+      SchemaProperty{.property_id = property_id_3, .type = type_1},
+      SchemaProperty{.property_id = property_id_4, .type = type_1},
+      SchemaProperty{.property_id = property_id_5, .type = type_1},
+      SchemaProperty{.property_id = property_id_6, .type = type_1},
+      SchemaProperty{.property_id = property_id_7, .type = type_1},
+  };
+
+  auto label_success = sm.InitializeNewLabel(label_name, schema, 1, sm.shard_map_version);
+  MG_ASSERT(label_success);
+
+  const LabelId label_id = sm.labels.at(label_name);
+  auto &label_space = sm.label_spaces.at(label_id);
+  Shards &shards_for_label = label_space.shards;
+  shards_for_label.clear();
+
+  auto key1 = memgraph::storage::v3::PropertyValue(0);
+  auto key2 = memgraph::storage::v3::PropertyValue(0);
+  CompoundKey compound_key_1 = {key1, key2};
+  shards_for_label[compound_key_1] = {};
+
+  auto key3 = memgraph::storage::v3::PropertyValue(12);
+  auto key4 = memgraph::storage::v3::PropertyValue(13);
+  CompoundKey compound_key_2 = {key3, key4};
+  shards_for_label[compound_key_2] = {};
+
+  sm.AllocateEdgeTypeIds(std::vector<memgraph::coordinator::EdgeTypeName>{"edge_type"});
+
+  return sm;
+}
+
+class ExpressionEvaluatorTest : public ::testing::Test {
+ public:
+  ExpressionEvaluatorTest() {}
+
+ protected:
+  AstStorage storage;
+  memgraph::utils::MonotonicBufferResource mem{1024};
+  EvaluationContext ctx{.memory = &mem, .timestamp = QueryTimestamp()};
+  SymbolTable symbol_table;
+
+  Frame frame{128};
+  std::unique_ptr<ShardRequestManagerInterface> shard_manager =
+      std::make_unique<MockedShardRequestManager>(CreateDummyShardmap());
+  ExpressionEvaluator eval{&frame, symbol_table, ctx, shard_manager.get(), memgraph::storage::v3::View::OLD};
+
+  Identifier *CreateIdentifierWithValue(std::string name, const TypedValue &value) {
+    auto id = storage.Create<Identifier>(name, true);
+    auto symbol = symbol_table.CreateSymbol(name, true);
+    id->MapTo(symbol);
+    frame[symbol] = value;
+    return id;
+  }
+
+  template <class TExpression>
+  auto Eval(TExpression *expr) {
+    ctx.properties = NamesToProperties(storage.properties_, shard_manager.get());
+    ctx.labels = NamesToLabels(storage.labels_, shard_manager.get());
+    auto value = expr->Accept(eval);
+    EXPECT_EQ(value.GetMemoryResource(), &mem) << "ExpressionEvaluator must use the MemoryResource from "
+                                                  "EvaluationContext for allocations!";
+    return value;
+  }
+};
+
+TEST_F(ExpressionEvaluatorTest, OrOperator) {
+  auto *op =
+      storage.Create<OrOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(false));
+  auto val1 = Eval(op);
+  ASSERT_EQ(val1.ValueBool(), true);
+  op = storage.Create<OrOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(true));
+  auto val2 = Eval(op);
+  ASSERT_EQ(val2.ValueBool(), true);
+}
+
+TEST_F(ExpressionEvaluatorTest, XorOperator) {
+  auto *op =
+      storage.Create<XorOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(false));
+  auto val1 = Eval(op);
+  ASSERT_EQ(val1.ValueBool(), true);
+  op = storage.Create<XorOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(true));
+  auto val2 = Eval(op);
+  ASSERT_EQ(val2.ValueBool(), false);
+}
+
+TEST_F(ExpressionEvaluatorTest, AndOperator) {
+  auto *op =
+      storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(true));
+  auto val1 = Eval(op);
+  ASSERT_EQ(val1.ValueBool(), true);
+  op = storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(false), storage.Create<PrimitiveLiteral>(true));
+  auto val2 = Eval(op);
+  ASSERT_EQ(val2.ValueBool(), false);
+}
+
+TEST_F(ExpressionEvaluatorTest, AndOperatorShortCircuit) {
+  {
+    auto *op =
+        storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(false), storage.Create<PrimitiveLiteral>(5));
+    auto value = Eval(op);
+    EXPECT_EQ(value.ValueBool(), false);
+  }
+  {
+    auto *op =
+        storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(5), storage.Create<PrimitiveLiteral>(false));
+    // We are evaluating left to right, so we don't short circuit here and
+    // raise due to `5`. This differs from neo4j, where they evaluate both
+    // sides and return `false` without checking for type of the first
+    // expression.
+    EXPECT_THROW(Eval(op), ExpressionRuntimeException);
+  }
+}
+
+TEST_F(ExpressionEvaluatorTest, AndOperatorNull) {
+  {
+    // Null doesn't short circuit
+    auto *op = storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(TypedValue()),
+                                           storage.Create<PrimitiveLiteral>(5));
+    EXPECT_THROW(Eval(op), ExpressionRuntimeException);
+  }
+  {
+    auto *op = storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(TypedValue()),
+                                           storage.Create<PrimitiveLiteral>(true));
+    auto value = Eval(op);
+    EXPECT_TRUE(value.IsNull());
+  }
+  {
+    auto *op = storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(TypedValue()),
+                                           storage.Create<PrimitiveLiteral>(false));
+    auto value = Eval(op);
+    ASSERT_TRUE(value.IsBool());
+    EXPECT_EQ(value.ValueBool(), false);
+  }
+}
+
+TEST_F(ExpressionEvaluatorTest, AdditionOperator) {
+  auto *op = storage.Create<AdditionOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3));
+  auto value = Eval(op);
+  ASSERT_EQ(value.ValueInt(), 5);
+}
+
+TEST_F(ExpressionEvaluatorTest, SubtractionOperator) {
+  auto *op =
+      storage.Create<SubtractionOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3));
+  auto value = Eval(op);
+  ASSERT_EQ(value.ValueInt(), -1);
+}
+
+TEST_F(ExpressionEvaluatorTest, MultiplicationOperator) {
+  auto *op =
+      storage.Create<MultiplicationOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3));
+  auto value = Eval(op);
+  ASSERT_EQ(value.ValueInt(), 6);
+}
+
+TEST_F(ExpressionEvaluatorTest, DivisionOperator) {
+  auto *op =
+      storage.Create<DivisionOperator>(storage.Create<PrimitiveLiteral>(50), storage.Create<PrimitiveLiteral>(10));
+  auto value = Eval(op);
+  ASSERT_EQ(value.ValueInt(), 5);
+}
+
+TEST_F(ExpressionEvaluatorTest, ModOperator) {
+  auto *op = storage.Create<ModOperator>(storage.Create<PrimitiveLiteral>(65), storage.Create<PrimitiveLiteral>(10));
+  auto value = Eval(op);
+  ASSERT_EQ(value.ValueInt(), 5);
+}
+
+TEST_F(ExpressionEvaluatorTest, EqualOperator) {
+  auto *op = storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15));
+  auto val1 = Eval(op);
+  ASSERT_EQ(val1.ValueBool(), false);
+  op = storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15));
+  auto val2 = Eval(op);
+  ASSERT_EQ(val2.ValueBool(), true);
+  op = storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15));
+  auto val3 = Eval(op);
+  ASSERT_EQ(val3.ValueBool(), false);
+}
+
+TEST_F(ExpressionEvaluatorTest, NotEqualOperator) {
+  auto *op =
+      storage.Create<NotEqualOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15));
+  auto val1 = Eval(op);
+  ASSERT_EQ(val1.ValueBool(), true);
+  op = storage.Create<NotEqualOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15));
+  auto val2 = Eval(op);
+  ASSERT_EQ(val2.ValueBool(), false);
+  op = storage.Create<NotEqualOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15));
+  auto val3 = Eval(op);
+  ASSERT_EQ(val3.ValueBool(), true);
+}
+
+TEST_F(ExpressionEvaluatorTest, LessOperator) {
+  auto *op = storage.Create<LessOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15));
+  auto val1 = Eval(op);
+  ASSERT_EQ(val1.ValueBool(), true);
+  op = storage.Create<LessOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15));
+  auto val2 = Eval(op);
+  ASSERT_EQ(val2.ValueBool(), false);
+  op = storage.Create<LessOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15));
+  auto val3 = Eval(op);
+  ASSERT_EQ(val3.ValueBool(), false);
+}
+
+TEST_F(ExpressionEvaluatorTest, GreaterOperator) {
+  auto *op =
+      storage.Create<GreaterOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15));
+  auto val1 = Eval(op);
+  ASSERT_EQ(val1.ValueBool(), false);
+  op = storage.Create<GreaterOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15));
+  auto val2 = Eval(op);
+  ASSERT_EQ(val2.ValueBool(), false);
+  op = storage.Create<GreaterOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15));
+  auto val3 = Eval(op);
+  ASSERT_EQ(val3.ValueBool(), true);
+}
+
+TEST_F(ExpressionEvaluatorTest, LessEqualOperator) {
+  auto *op =
+      storage.Create<LessEqualOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15));
+  auto val1 = Eval(op);
+  ASSERT_EQ(val1.ValueBool(), true);
+  op = storage.Create<LessEqualOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15));
+  auto val2 = Eval(op);
+  ASSERT_EQ(val2.ValueBool(), true);
+  op = storage.Create<LessEqualOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15));
+  auto val3 = Eval(op);
+  ASSERT_EQ(val3.ValueBool(), false);
+}
+
+TEST_F(ExpressionEvaluatorTest, GreaterEqualOperator) {
+  auto *op =
+      storage.Create<GreaterEqualOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15));
+  auto val1 = Eval(op);
+  ASSERT_EQ(val1.ValueBool(), false);
+  op = storage.Create<GreaterEqualOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15));
+  auto val2 = Eval(op);
+  ASSERT_EQ(val2.ValueBool(), true);
+  op = storage.Create<GreaterEqualOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15));
+  auto val3 = Eval(op);
+  ASSERT_EQ(val3.ValueBool(), true);
+}
+
+TEST_F(ExpressionEvaluatorTest, InListOperator) {
+  auto *list_literal = storage.Create<ListLiteral>(std::vector<Expression *>{
+      storage.Create<PrimitiveLiteral>(1), storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>("a")});
+  {
+    // Element exists in list.
+    auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>(2), list_literal);
+    auto value = Eval(op);
+    EXPECT_EQ(value.ValueBool(), true);
+  }
+  {
+    // Element doesn't exist in list.
+    auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>("x"), list_literal);
+    auto value = Eval(op);
+    EXPECT_EQ(value.ValueBool(), false);
+  }
+  {
+    auto *list_literal = storage.Create<ListLiteral>(
+        std::vector<Expression *>{storage.Create<PrimitiveLiteral>(TypedValue()), storage.Create<PrimitiveLiteral>(2),
+                                  storage.Create<PrimitiveLiteral>("a")});
+    // Element doesn't exist in list with null element.
+    auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>("x"), list_literal);
+    auto value = Eval(op);
+    EXPECT_TRUE(value.IsNull());
+  }
+  {
+    // Null list.
+    auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>("x"),
+                                              storage.Create<PrimitiveLiteral>(TypedValue()));
+    auto value = Eval(op);
+    EXPECT_TRUE(value.IsNull());
+  }
+  {
+    // Null literal.
+    auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>(TypedValue()), list_literal);
+    auto value = Eval(op);
+    EXPECT_TRUE(value.IsNull());
+  }
+  {
+    // Null literal, empty list.
+    auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>(TypedValue()),
+                                              storage.Create<ListLiteral>(std::vector<Expression *>()));
+    auto value = Eval(op);
+    EXPECT_FALSE(value.ValueBool());
+  }
+}
+
+TEST_F(ExpressionEvaluatorTest, ListIndexing) {
+  auto *list_literal = storage.Create<ListLiteral>(
+      std::vector<Expression *>{storage.Create<PrimitiveLiteral>(1), storage.Create<PrimitiveLiteral>(2),
+                                storage.Create<PrimitiveLiteral>(3), storage.Create<PrimitiveLiteral>(4)});
+  {
+    // Legal indexing.
+    auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>(2));
+    auto value = Eval(op);
+    EXPECT_EQ(value.ValueInt(), 3);
+  }
+  {
+    // Out of bounds indexing.
+    auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>(4));
+    auto value = Eval(op);
+    EXPECT_TRUE(value.IsNull());
+  }
+  {
+    // Out of bounds indexing with negative bound.
+    auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>(-100));
+    auto value = Eval(op);
+    EXPECT_TRUE(value.IsNull());
+  }
+  {
+    // Legal indexing with negative index.
+    auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>(-2));
+    auto value = Eval(op);
+    EXPECT_EQ(value.ValueInt(), 3);
+  }
+  {
+    // Indexing with one operator being null.
+    auto *op = storage.Create<SubscriptOperator>(storage.Create<PrimitiveLiteral>(TypedValue()),
+                                                 storage.Create<PrimitiveLiteral>(-2));
+    auto value = Eval(op);
+    EXPECT_TRUE(value.IsNull());
+  }
+  {
+    // Indexing with incompatible type.
+    auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>("bla"));
+    EXPECT_THROW(Eval(op), ExpressionRuntimeException);
+  }
+}
+
+TEST_F(ExpressionEvaluatorTest, MapIndexing) {
+  auto *map_literal = storage.Create<MapLiteral>(
+      std::unordered_map<PropertyIx, Expression *>{{storage.GetPropertyIx("a"), storage.Create<PrimitiveLiteral>(1)},
+                                                   {storage.GetPropertyIx("b"), storage.Create<PrimitiveLiteral>(2)},
+                                                   {storage.GetPropertyIx("c"), storage.Create<PrimitiveLiteral>(3)}});
+  {
+    // Legal indexing.
+    auto *op = storage.Create<SubscriptOperator>(map_literal, storage.Create<PrimitiveLiteral>("b"));
+    auto value = Eval(op);
+    EXPECT_EQ(value.ValueInt(), 2);
+  }
+  {
+    // Legal indexing, non-existing key.
+    auto *op = storage.Create<SubscriptOperator>(map_literal, storage.Create<PrimitiveLiteral>("z"));
+    auto value = Eval(op);
+    EXPECT_TRUE(value.IsNull());
+  }
+  {
+    // Wrong key type.
+    auto *op = storage.Create<SubscriptOperator>(map_literal, storage.Create<PrimitiveLiteral>(42));
+    EXPECT_THROW(Eval(op), ExpressionRuntimeException);
+  }
+  {
+    // Indexing with Null.
+    auto *op = storage.Create<SubscriptOperator>(map_literal, storage.Create<PrimitiveLiteral>(TypedValue()));
+    auto value = Eval(op);
+    EXPECT_TRUE(value.IsNull());
+  }
+}
+
+using Vertex = memgraph::msgs::Vertex;
+using Edge = memgraph::msgs::Edge;
+using EdgeType = memgraph::msgs::EdgeType;
+using EdgeId = memgraph::msgs::EdgeId;
+using Value = memgraph::msgs::Value;
+using VertexId = memgraph::msgs::VertexId;
+using Label = memgraph::msgs::Label;
+
+accessors::VertexAccessor CreateVertex(std::vector<std::pair<PropertyId, Value>> props,
+                                       const ShardRequestManagerInterface *manager, Vertex v = {}) {
+  return {std::move(v), std::move(props), manager};
+}
+
+accessors::EdgeAccessor CreateEdge(std::vector<std::pair<PropertyId, Value>> props,
+                                   const ShardRequestManagerInterface *manager, EdgeId edge_id = {}, VertexId src = {},
+                                   VertexId dst = {}) {
+  auto edge = Edge{.src = std::move(src),
+                   .dst = std::move(dst),
+                   .properties = std::move(props),
+                   .id = edge_id,
+                   .type = EdgeType{manager->NameToEdgeType("edge_type")}};
+  return accessors::EdgeAccessor{std::move(edge), manager};
+}
+
+TEST_F(ExpressionEvaluatorTest, VertexAndEdgeIndexing) {
+  auto prop = shard_manager->NameToProperty("prop");
+  auto vertex = CreateVertex({{prop, Value(static_cast<int64_t>(42))}}, shard_manager.get(), {});
+  auto edge = CreateEdge({{prop, Value(static_cast<int64_t>(43))}}, shard_manager.get(), {}, {}, {});
+
+  auto *vertex_id = CreateIdentifierWithValue("v1", TypedValue(vertex));
+  auto *edge_id = CreateIdentifierWithValue("e11", TypedValue(edge));
+  {
+    // Legal indexing.
+    auto *op1 = storage.Create<SubscriptOperator>(vertex_id, storage.Create<PrimitiveLiteral>("prop"));
+    auto value1 = Eval(op1);
+    EXPECT_EQ(value1.ValueInt(), 42);
+
+    auto *op2 = storage.Create<SubscriptOperator>(edge_id, storage.Create<PrimitiveLiteral>("prop"));
+    auto value2 = Eval(op2);
+    EXPECT_EQ(value2.ValueInt(), 43);
+  }
+
+  {
+    // Legal indexing, non-existing key.
+    auto *op1 = storage.Create<SubscriptOperator>(vertex_id, storage.Create<PrimitiveLiteral>("blah"));
+    auto value1 = Eval(op1);
+    EXPECT_TRUE(value1.IsNull());
+    auto *op2 = storage.Create<SubscriptOperator>(edge_id, storage.Create<PrimitiveLiteral>("blah"));
+    auto value2 = Eval(op2);
+    EXPECT_TRUE(value2.IsNull());
+  }
+  {
+    // Wrong key type.
+    auto *op1 = storage.Create<SubscriptOperator>(vertex_id, storage.Create<PrimitiveLiteral>(1));
+    EXPECT_THROW(Eval(op1), ExpressionRuntimeException);
+
+    auto *op2 = storage.Create<SubscriptOperator>(edge_id, storage.Create<PrimitiveLiteral>(1));
+    EXPECT_THROW(Eval(op2), ExpressionRuntimeException);
+  }
+  {
+    // Indexing with Null.
+    auto *op1 = storage.Create<SubscriptOperator>(vertex_id, storage.Create<PrimitiveLiteral>(TypedValue{}));
+    auto value1 = Eval(op1);
+    EXPECT_TRUE(value1.IsNull());
+
+    auto *op2 = storage.Create<SubscriptOperator>(edge_id, storage.Create<PrimitiveLiteral>(TypedValue{}));
+    auto value2 = Eval(op2);
+    EXPECT_TRUE(value2.IsNull());
+  }
+}
+
+TEST_F(ExpressionEvaluatorTest, ListSlicingOperator) {
+  auto *list_literal = storage.Create<ListLiteral>(
+      std::vector<Expression *>{storage.Create<PrimitiveLiteral>(1), storage.Create<PrimitiveLiteral>(2),
+                                storage.Create<PrimitiveLiteral>(3), storage.Create<PrimitiveLiteral>(4)});
+
+  auto extract_ints = [](TypedValue list) {
+    std::vector<int64_t> int_list;
+    for (auto x : list.ValueList()) {
+      int_list.push_back(x.ValueInt());
+    }
+    return int_list;
+  };
+  {
+    // Legal slicing with both bounds defined.
+    auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(2),
+                                                   storage.Create<PrimitiveLiteral>(4));
+    auto value = Eval(op);
+    EXPECT_THAT(extract_ints(value), ElementsAre(3, 4));
+  }
+  {
+    // Legal slicing with negative bound.
+    auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(2),
+                                                   storage.Create<PrimitiveLiteral>(-1));
+    auto value = Eval(op);
+    EXPECT_THAT(extract_ints(value), ElementsAre(3));
+  }
+  {
+    // Lower bound larger than upper bound.
+    auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(2),
+                                                   storage.Create<PrimitiveLiteral>(-4));
+    auto value = Eval(op);
+    EXPECT_THAT(extract_ints(value), ElementsAre());
+  }
+  {
+    // Bounds ouf or range.
+    auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(-100),
+                                                   storage.Create<PrimitiveLiteral>(10));
+    auto value = Eval(op);
+    EXPECT_THAT(extract_ints(value), ElementsAre(1, 2, 3, 4));
+  }
+  {
+    // Lower bound undefined.
+    auto *op = storage.Create<ListSlicingOperator>(list_literal, nullptr, storage.Create<PrimitiveLiteral>(3));
+    auto value = Eval(op);
+    EXPECT_THAT(extract_ints(value), ElementsAre(1, 2, 3));
+  }
+  {
+    // Upper bound undefined.
+    auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(-2), nullptr);
+    auto value = Eval(op);
+    EXPECT_THAT(extract_ints(value), ElementsAre(3, 4));
+  }
+  {
+    // Bound of illegal type and null value bound.
+    auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(TypedValue()),
+                                                   storage.Create<PrimitiveLiteral>("mirko"));
+    EXPECT_THROW(Eval(op), ExpressionRuntimeException);
+  }
+  {
+    // List of illegal type.
+    auto *op = storage.Create<ListSlicingOperator>(storage.Create<PrimitiveLiteral>("a"),
+                                                   storage.Create<PrimitiveLiteral>(-2), nullptr);
+    EXPECT_THROW(Eval(op), ExpressionRuntimeException);
+  }
+  {
+    // Null value list with undefined upper bound.
+    auto *op = storage.Create<ListSlicingOperator>(storage.Create<PrimitiveLiteral>(TypedValue()),
+                                                   storage.Create<PrimitiveLiteral>(-2), nullptr);
+    auto value = Eval(op);
+    EXPECT_TRUE(value.IsNull());
+    ;
+  }
+  {
+    // Null value index.
+    auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(-2),
+                                                   storage.Create<PrimitiveLiteral>(TypedValue()));
+    auto value = Eval(op);
+    EXPECT_TRUE(value.IsNull());
+    ;
+  }
+}
+
+TEST_F(ExpressionEvaluatorTest, IfOperator) {
+  auto *then_expression = storage.Create<PrimitiveLiteral>(10);
+  auto *else_expression = storage.Create<PrimitiveLiteral>(20);
+  {
+    auto *condition_true =
+        storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(2));
+    auto *op = storage.Create<IfOperator>(condition_true, then_expression, else_expression);
+    auto value = Eval(op);
+    ASSERT_EQ(value.ValueInt(), 10);
+  }
+  {
+    auto *condition_false =
+        storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3));
+    auto *op = storage.Create<IfOperator>(condition_false, then_expression, else_expression);
+    auto value = Eval(op);
+    ASSERT_EQ(value.ValueInt(), 20);
+  }
+  {
+    auto *condition_exception =
+        storage.Create<AdditionOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3));
+    auto *op = storage.Create<IfOperator>(condition_exception, then_expression, else_expression);
+    ASSERT_THROW(Eval(op), ExpressionRuntimeException);
+  }
+}
+
+TEST_F(ExpressionEvaluatorTest, NotOperator) {
+  auto *op = storage.Create<NotOperator>(storage.Create<PrimitiveLiteral>(false));
+  auto value = Eval(op);
+  ASSERT_EQ(value.ValueBool(), true);
+}
+
+TEST_F(ExpressionEvaluatorTest, UnaryPlusOperator) {
+  auto *op = storage.Create<UnaryPlusOperator>(storage.Create<PrimitiveLiteral>(5));
+  auto value = Eval(op);
+  ASSERT_EQ(value.ValueInt(), 5);
+}
+
+TEST_F(ExpressionEvaluatorTest, UnaryMinusOperator) {
+  auto *op = storage.Create<UnaryMinusOperator>(storage.Create<PrimitiveLiteral>(5));
+  auto value = Eval(op);
+  ASSERT_EQ(value.ValueInt(), -5);
+}
+
+TEST_F(ExpressionEvaluatorTest, IsNullOperator) {
+  auto *op = storage.Create<IsNullOperator>(storage.Create<PrimitiveLiteral>(1));
+  auto val1 = Eval(op);
+  ASSERT_EQ(val1.ValueBool(), false);
+  op = storage.Create<IsNullOperator>(storage.Create<PrimitiveLiteral>(TypedValue()));
+  auto val2 = Eval(op);
+  ASSERT_EQ(val2.ValueBool(), true);
+}
+
+TEST_F(ExpressionEvaluatorTest, LabelsTest) {
+  Label label{shard_manager->NameToLabel("label1")};
+  Vertex vertex = {{}, {label}};
+  auto v1 = CreateVertex({}, shard_manager.get(), vertex);
+  auto *identifier = storage.Create<Identifier>("n");
+  auto node_symbol = symbol_table.CreateSymbol("n", true);
+  identifier->MapTo(node_symbol);
+  frame[node_symbol] = TypedValue(v1);
+  {
+    auto *op = storage.Create<LabelsTest>(identifier, std::vector<LabelIx>{LabelIx{"label1", label.id.AsInt()}});
+    auto value = Eval(op);
+    EXPECT_EQ(value.ValueBool(), true);
+  }
+  {
+    auto *op = storage.Create<LabelsTest>(identifier, std::vector<LabelIx>{LabelIx{"label2", 10}});
+    auto value = Eval(op);
+    EXPECT_EQ(value.ValueBool(), false);
+  }
+  {
+    auto *op = storage.Create<LabelsTest>(identifier, std::vector<LabelIx>{LabelIx{"label2", 10}});
+    frame[node_symbol] = TypedValue();
+    auto value = Eval(op);
+    EXPECT_TRUE(value.IsNull());
+  }
+}
+
+TEST_F(ExpressionEvaluatorTest, Aggregation) {
+  auto aggr = storage.Create<Aggregation>(storage.Create<PrimitiveLiteral>(42), nullptr, Aggregation::Op::COUNT);
+  auto aggr_sym = symbol_table.CreateSymbol("aggr", true);
+  aggr->MapTo(aggr_sym);
+  frame[aggr_sym] = TypedValue(1);
+  auto value = Eval(aggr);
+  EXPECT_EQ(value.ValueInt(), 1);
+}
+
+TEST_F(ExpressionEvaluatorTest, ListLiteral) {
+  auto *list_literal = storage.Create<ListLiteral>(std::vector<Expression *>{storage.Create<PrimitiveLiteral>(1),
+                                                                             storage.Create<PrimitiveLiteral>("bla"),
+                                                                             storage.Create<PrimitiveLiteral>(true)});
+  TypedValue result = Eval(list_literal);
+  ASSERT_TRUE(result.IsList());
+  auto &result_elems = result.ValueList();
+  ASSERT_EQ(3, result_elems.size());
+  EXPECT_TRUE(result_elems[0].IsInt());
+  EXPECT_TRUE(result_elems[1].IsString());
+  EXPECT_TRUE(result_elems[2].IsBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, ParameterLookup) {
+  ctx.parameters.Add(0, memgraph::storage::v3::PropertyValue(42));
+  auto *param_lookup = storage.Create<ParameterLookup>(0);
+  auto value = Eval(param_lookup);
+  ASSERT_TRUE(value.IsInt());
+  EXPECT_EQ(value.ValueInt(), 42);
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionAll1) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *all = ALL("x", LIST(LITERAL(1), LITERAL(1)), WHERE(EQ(ident_x, LITERAL(1))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  all->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(all);
+  ASSERT_TRUE(value.IsBool());
+  EXPECT_TRUE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionAll2) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *all = ALL("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(1))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  all->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(all);
+  ASSERT_TRUE(value.IsBool());
+  EXPECT_FALSE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionAllNullList) {
+  AstStorage storage;
+  auto *all = ALL("x", LITERAL(TypedValue()), WHERE(LITERAL(true)));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  all->identifier_->MapTo(x_sym);
+  auto value = Eval(all);
+  EXPECT_TRUE(value.IsNull());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionAllNullElementInList1) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *all = ALL("x", LIST(LITERAL(1), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(1))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  all->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(all);
+  ASSERT_TRUE(value.IsBool());
+  EXPECT_FALSE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionAllNullElementInList2) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *all = ALL("x", LIST(LITERAL(2), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(1))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  all->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(all);
+  ASSERT_TRUE(value.IsBool());
+  EXPECT_FALSE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionAllWhereWrongType) {
+  AstStorage storage;
+  auto *all = ALL("x", LIST(LITERAL(1)), WHERE(LITERAL(2)));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  all->identifier_->MapTo(x_sym);
+  EXPECT_THROW(Eval(all), ExpressionRuntimeException);
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionSingle1) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *single = SINGLE("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(1))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  single->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(single);
+  ASSERT_TRUE(value.IsBool());
+  EXPECT_TRUE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionSingle2) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *single = SINGLE("x", LIST(LITERAL(1), LITERAL(2)), WHERE(GREATER(ident_x, LITERAL(0))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  single->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(single);
+  ASSERT_TRUE(value.IsBool());
+  EXPECT_FALSE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionSingleNullList) {
+  AstStorage storage;
+  auto *single = SINGLE("x", LITERAL(TypedValue()), WHERE(LITERAL(true)));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  single->identifier_->MapTo(x_sym);
+  auto value = Eval(single);
+  EXPECT_TRUE(value.IsNull());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionSingleNullElementInList1) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *single = SINGLE("x", LIST(LITERAL(1), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(1))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  single->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(single);
+  ASSERT_TRUE(value.IsBool());
+  EXPECT_TRUE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionSingleNullElementInList2) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *single = SINGLE("x", LIST(LITERAL(2), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(1))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  single->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(single);
+  ASSERT_TRUE(value.IsBool());
+  EXPECT_FALSE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionAny1) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *any = ANY("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(1))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  any->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(any);
+  ASSERT_TRUE(value.IsBool());
+  EXPECT_TRUE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionAny2) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *any = ANY("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(0))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  any->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(any);
+  ASSERT_TRUE(value.IsBool());
+  EXPECT_FALSE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionAnyNullList) {
+  AstStorage storage;
+  auto *any = ANY("x", LITERAL(TypedValue()), WHERE(LITERAL(true)));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  any->identifier_->MapTo(x_sym);
+  auto value = Eval(any);
+  EXPECT_TRUE(value.IsNull());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionAnyNullElementInList1) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *any = ANY("x", LIST(LITERAL(0), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(0))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  any->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(any);
+  EXPECT_TRUE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionAnyNullElementInList2) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *any = ANY("x", LIST(LITERAL(1), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(0))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  any->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(any);
+  EXPECT_FALSE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionAnyWhereWrongType) {
+  AstStorage storage;
+  auto *any = ANY("x", LIST(LITERAL(1)), WHERE(LITERAL(2)));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  any->identifier_->MapTo(x_sym);
+  EXPECT_THROW(Eval(any), ExpressionRuntimeException);
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionNone1) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *none = NONE("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(0))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  none->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(none);
+  ASSERT_TRUE(value.IsBool());
+  EXPECT_TRUE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionNone2) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *none = NONE("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(1))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  none->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(none);
+  ASSERT_TRUE(value.IsBool());
+  EXPECT_FALSE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionNoneNullList) {
+  AstStorage storage;
+  auto *none = NONE("x", LITERAL(TypedValue()), WHERE(LITERAL(true)));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  none->identifier_->MapTo(x_sym);
+  auto value = Eval(none);
+  EXPECT_TRUE(value.IsNull());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionNoneNullElementInList1) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *any = NONE("x", LIST(LITERAL(1), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(0))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  any->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(any);
+  EXPECT_TRUE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionNoneNullElementInList2) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *none = NONE("x", LIST(LITERAL(0), LITERAL(TypedValue())), WHERE(EQ(ident_x, LITERAL(0))));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  none->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(none);
+  EXPECT_FALSE(value.ValueBool());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionNoneWhereWrongType) {
+  AstStorage storage;
+  auto *none = NONE("x", LIST(LITERAL(1)), WHERE(LITERAL(2)));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  none->identifier_->MapTo(x_sym);
+  EXPECT_THROW(Eval(none), ExpressionRuntimeException);
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionReduce) {
+  AstStorage storage;
+  auto *ident_sum = IDENT("sum");
+  auto *ident_x = IDENT("x");
+  auto *reduce = REDUCE("sum", LITERAL(0), "x", LIST(LITERAL(1), LITERAL(2)), ADD(ident_sum, ident_x));
+  const auto sum_sym = symbol_table.CreateSymbol("sum", true);
+  reduce->accumulator_->MapTo(sum_sym);
+  ident_sum->MapTo(sum_sym);
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  reduce->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(reduce);
+  ASSERT_TRUE(value.IsInt());
+  EXPECT_EQ(value.ValueInt(), 3);
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionExtract) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *extract = EXTRACT("x", LIST(LITERAL(1), LITERAL(2), LITERAL(TypedValue())), ADD(ident_x, LITERAL(1)));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  extract->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(extract);
+  EXPECT_TRUE(value.IsList());
+  ;
+  auto result = value.ValueList();
+  EXPECT_EQ(result[0].ValueInt(), 2);
+  EXPECT_EQ(result[1].ValueInt(), 3);
+  EXPECT_TRUE(result[2].IsNull());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionExtractNull) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *extract = EXTRACT("x", LITERAL(TypedValue()), ADD(ident_x, LITERAL(1)));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  extract->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  auto value = Eval(extract);
+  EXPECT_TRUE(value.IsNull());
+}
+
+TEST_F(ExpressionEvaluatorTest, FunctionExtractExceptions) {
+  AstStorage storage;
+  auto *ident_x = IDENT("x");
+  auto *extract = EXTRACT("x", LITERAL("bla"), ADD(ident_x, LITERAL(1)));
+  const auto x_sym = symbol_table.CreateSymbol("x", true);
+  extract->identifier_->MapTo(x_sym);
+  ident_x->MapTo(x_sym);
+  EXPECT_THROW(Eval(extract), ExpressionRuntimeException);
+}
+
+TEST_F(ExpressionEvaluatorTest, RegexMatchInvalidArguments) {
+  EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL(TypedValue()), LITERAL("regex"))).IsNull());
+  EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL(3), LITERAL("regex"))).IsNull());
+  EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LIST(LITERAL("string")), LITERAL("regex"))).IsNull());
+  EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL("string"), LITERAL(TypedValue()))).IsNull());
+  EXPECT_THROW(Eval(storage.Create<RegexMatch>(LITERAL("string"), LITERAL(42))), ExpressionRuntimeException);
+  EXPECT_THROW(Eval(storage.Create<RegexMatch>(LITERAL("string"), LIST(LITERAL("regex")))), ExpressionRuntimeException);
+}
+
+TEST_F(ExpressionEvaluatorTest, RegexMatchInvalidRegex) {
+  EXPECT_THROW(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL("*ext"))), ExpressionRuntimeException);
+  EXPECT_THROW(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL("[ext"))), ExpressionRuntimeException);
+}
+
+TEST_F(ExpressionEvaluatorTest, RegexMatch) {
+  EXPECT_FALSE(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL(".*ex"))).ValueBool());
+  EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL(".*ext"))).ValueBool());
+  EXPECT_FALSE(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL("[ext]"))).ValueBool());
+  EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL(".+[ext]"))).ValueBool());
+}
+
+class ExpressionEvaluatorPropertyLookup : public ExpressionEvaluatorTest {
+ protected:
+  std::pair<std::string, memgraph::storage::v3::PropertyId> prop_age =
+      std::make_pair("age", shard_manager->NameToProperty("age"));
+  std::pair<std::string, memgraph::storage::v3::PropertyId> prop_height =
+      std::make_pair("height", shard_manager->NameToProperty("height"));
+  Identifier *identifier = storage.Create<Identifier>("element");
+  Symbol symbol = symbol_table.CreateSymbol("element", true);
+
+  void SetUp() { identifier->MapTo(symbol); }
+
+  auto Value(std::pair<std::string, memgraph::storage::v3::PropertyId> property) {
+    auto *op = storage.Create<PropertyLookup>(identifier, storage.GetPropertyIx(property.first));
+    return Eval(op);
+  }
+};
+
+TEST_F(ExpressionEvaluatorPropertyLookup, Null) {
+  frame[symbol] = TypedValue();
+  EXPECT_TRUE(Value(prop_age).IsNull());
+}
+
+TEST_F(ExpressionEvaluatorPropertyLookup, MapLiteral) {
+  frame[symbol] = TypedValue(std::map<std::string, TypedValue>{{prop_age.first, TypedValue(10)}});
+  EXPECT_EQ(Value(prop_age).ValueInt(), 10);
+  EXPECT_TRUE(Value(prop_height).IsNull());
+}
+
+class FunctionTest : public ExpressionEvaluatorTest {
+ protected:
+  std::vector<Expression *> ExpressionsFromTypedValues(const std::vector<TypedValue> &tvs) {
+    std::vector<Expression *> expressions;
+    expressions.reserve(tvs.size());
+
+    for (size_t i = 0; i < tvs.size(); ++i) {
+      auto *ident = storage.Create<Identifier>("arg_" + std::to_string(i), true);
+      auto sym = symbol_table.CreateSymbol("arg_" + std::to_string(i), true);
+      ident->MapTo(sym);
+      frame[sym] = tvs[i];
+      expressions.push_back(ident);
+    }
+
+    return expressions;
+  }
+
+  TypedValue EvaluateFunctionWithExprs(const std::string &function_name, const std::vector<Expression *> &expressions) {
+    auto *op = storage.Create<Function>(function_name, expressions);
+    return Eval(op);
+  }
+
+  template <class... TArgs>
+  TypedValue EvaluateFunction(const std::string &function_name, std::tuple<TArgs...> args) {
+    std::vector<TypedValue> tv_args;
+    tv_args.reserve(args.size());
+    for (auto &arg : args) tv_args.emplace_back(std::move(arg));
+    return EvaluateFunctionWithExprs(function_name, ExpressionsFromTypedValues(tv_args));
+  }
+
+  template <class... TArgs>
+  TypedValue EvaluateFunction(const std::string &function_name, TArgs &&...args) {
+    return EvaluateFunctionWithExprs(function_name,
+                                     ExpressionsFromTypedValues(std::vector<TypedValue>{TypedValue(args)...}));
+  }
+};
+
+template <class... TArgs>
+static TypedValue MakeTypedValueList(TArgs &&...args) {
+  return TypedValue(std::vector<TypedValue>{TypedValue(args)...});
+}
+
+TEST_F(FunctionTest, EndNode) {
+  ASSERT_THROW(EvaluateFunction("ENDNODE"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("ENDNODE", TypedValue()).IsNull());
+  Label l{shard_manager->NameToLabel("label1")};
+  EdgeId e_id{10};
+  VertexId dst{l, {msgs::Value(static_cast<int64_t>(2))}};
+  auto e = CreateEdge({}, shard_manager.get(), e_id, {}, dst);
+  const auto res = EvaluateFunction("ENDNODE", e).ValueVertex().Id();
+  ASSERT_EQ(res, dst);
+  ASSERT_THROW(EvaluateFunction("ENDNODE", 2), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, Head) {
+  ASSERT_THROW(EvaluateFunction("HEAD"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("HEAD", TypedValue()).IsNull());
+  auto argument = MakeTypedValueList(3, 4, 5);
+  ASSERT_EQ(EvaluateFunction("HEAD", argument).ValueInt(), 3);
+  argument.ValueList().clear();
+  ASSERT_TRUE(EvaluateFunction("HEAD", argument).IsNull());
+  ASSERT_THROW(EvaluateFunction("HEAD", 2), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, Properties) {
+  ASSERT_THROW(EvaluateFunction("PROPERTIES"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("PROPERTIES", TypedValue()).IsNull());
+  const auto height = shard_manager->NameToProperty("height");
+  const auto age = shard_manager->NameToProperty("age");
+  auto v1 = CreateVertex({{height, Value(static_cast<int64_t>(5))}, {age, Value(static_cast<int64_t>(10))}},
+                         shard_manager.get());
+  auto e = CreateEdge({{height, Value(static_cast<int64_t>(3))}, {age, Value(static_cast<int64_t>(15))}},
+                      shard_manager.get());
+
+  auto prop_values_to_int = [](TypedValue t) {
+    std::unordered_map<std::string, int> properties;
+    for (auto property : t.ValueMap()) {
+      properties[std::string(property.first)] = property.second.ValueInt();
+    }
+    return properties;
+  };
+
+  ASSERT_THAT(prop_values_to_int(EvaluateFunction("PROPERTIES", v1)),
+              UnorderedElementsAre(testing::Pair("height", 5), testing::Pair("age", 10)));
+  ASSERT_THAT(prop_values_to_int(EvaluateFunction("PROPERTIES", e)),
+              UnorderedElementsAre(testing::Pair("height", 3), testing::Pair("age", 15)));
+  ASSERT_THROW(EvaluateFunction("PROPERTIES", 2), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, Last) {
+  ASSERT_THROW(EvaluateFunction("LAST"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("LAST", TypedValue()).IsNull());
+  auto argument = MakeTypedValueList(3, 4, 5);
+  ASSERT_EQ(EvaluateFunction("LAST", argument).ValueInt(), 5);
+  argument.ValueList().clear();
+  ASSERT_TRUE(EvaluateFunction("LAST", argument).IsNull());
+  ASSERT_THROW(EvaluateFunction("LAST", 5), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, Size) {
+  ASSERT_THROW(EvaluateFunction("SIZE"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("SIZE", TypedValue()).IsNull());
+  auto argument = MakeTypedValueList(3, 4, 5);
+  ASSERT_EQ(EvaluateFunction("SIZE", argument).ValueInt(), 3);
+  ASSERT_EQ(EvaluateFunction("SIZE", "john").ValueInt(), 4);
+  ASSERT_EQ(EvaluateFunction("SIZE",
+                             std::map<std::string, TypedValue>{
+                                 {"a", TypedValue(5)}, {"b", TypedValue(true)}, {"c", TypedValue("123")}})
+                .ValueInt(),
+            3);
+  ASSERT_THROW(EvaluateFunction("SIZE", 5), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, StartNode) {
+  ASSERT_THROW(EvaluateFunction("STARTNODE"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("STARTNODE", TypedValue()).IsNull());
+  Label l{shard_manager->NameToLabel("label1")};
+  EdgeId e_id{5};
+  VertexId src{l, {msgs::Value(static_cast<int64_t>(4))}};
+  auto e = CreateEdge({}, shard_manager.get(), e_id, src);
+  const auto res = EvaluateFunction("STARTNODE", e).ValueVertex().Id();
+  ASSERT_EQ(res, src);
+  ASSERT_THROW(EvaluateFunction("STARTNODE", 2), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, ToBoolean) {
+  ASSERT_THROW(EvaluateFunction("TOBOOLEAN"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("TOBOOLEAN", TypedValue()).IsNull());
+  ASSERT_EQ(EvaluateFunction("TOBOOLEAN", 123).ValueBool(), true);
+  ASSERT_EQ(EvaluateFunction("TOBOOLEAN", -213).ValueBool(), true);
+  ASSERT_EQ(EvaluateFunction("TOBOOLEAN", 0).ValueBool(), false);
+  ASSERT_EQ(EvaluateFunction("TOBOOLEAN", " trUE \n\t").ValueBool(), true);
+  ASSERT_EQ(EvaluateFunction("TOBOOLEAN", "\n\tFalsE").ValueBool(), false);
+  ASSERT_TRUE(EvaluateFunction("TOBOOLEAN", "\n\tFALSEA ").IsNull());
+  ASSERT_EQ(EvaluateFunction("TOBOOLEAN", true).ValueBool(), true);
+  ASSERT_EQ(EvaluateFunction("TOBOOLEAN", false).ValueBool(), false);
+}
+
+TEST_F(FunctionTest, ToFloat) {
+  ASSERT_THROW(EvaluateFunction("TOFLOAT"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("TOFLOAT", TypedValue()).IsNull());
+  ASSERT_EQ(EvaluateFunction("TOFLOAT", " -3.5 \n\t").ValueDouble(), -3.5);
+  ASSERT_EQ(EvaluateFunction("TOFLOAT", "\n\t0.5e-1").ValueDouble(), 0.05);
+  ASSERT_TRUE(EvaluateFunction("TOFLOAT", "\n\t3.4e-3X ").IsNull());
+  ASSERT_EQ(EvaluateFunction("TOFLOAT", -3.5).ValueDouble(), -3.5);
+  ASSERT_EQ(EvaluateFunction("TOFLOAT", -3).ValueDouble(), -3.0);
+  ASSERT_THROW(EvaluateFunction("TOFLOAT", true), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, ToInteger) {
+  ASSERT_THROW(EvaluateFunction("TOINTEGER"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("TOINTEGER", TypedValue()).IsNull());
+  ASSERT_EQ(EvaluateFunction("TOINTEGER", false).ValueInt(), 0);
+  ASSERT_EQ(EvaluateFunction("TOINTEGER", true).ValueInt(), 1);
+  ASSERT_EQ(EvaluateFunction("TOINTEGER", "\n\t3").ValueInt(), 3);
+  ASSERT_EQ(EvaluateFunction("TOINTEGER", " -3.5 \n\t").ValueInt(), -3);
+  ASSERT_TRUE(EvaluateFunction("TOINTEGER", "\n\t3X ").IsNull());
+  ASSERT_EQ(EvaluateFunction("TOINTEGER", -3.5).ValueInt(), -3);
+  ASSERT_EQ(EvaluateFunction("TOINTEGER", 3.5).ValueInt(), 3);
+}
+
+TEST_F(FunctionTest, Type) {
+  ASSERT_THROW(EvaluateFunction("TYPE"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("TYPE", TypedValue()).IsNull());
+  auto e = CreateEdge({}, shard_manager.get());
+  ASSERT_EQ(EvaluateFunction("TYPE", e).ValueString(), "edge_type");
+  ASSERT_THROW(EvaluateFunction("TYPE", 2), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, ValueType) {
+  ASSERT_THROW(EvaluateFunction("VALUETYPE"), FunctionRuntimeException);
+  ASSERT_THROW(EvaluateFunction("VALUETYPE", TypedValue(), TypedValue()), FunctionRuntimeException);
+  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 = CreateVertex({}, shard_manager.get());
+  ASSERT_EQ(EvaluateFunction("VALUETYPE", v1).ValueString(), "NODE");
+  auto e = CreateEdge({}, shard_manager.get());
+  ASSERT_EQ(EvaluateFunction("VALUETYPE", e).ValueString(), "RELATIONSHIP");
+}
+
+TEST_F(FunctionTest, Labels) {
+  ASSERT_THROW(EvaluateFunction("LABELS"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("LABELS", TypedValue()).IsNull());
+  Label label{shard_manager->NameToLabel("label1")};
+  auto v = CreateVertex({}, shard_manager.get(), {{}, {label}});
+  std::vector<std::string> labels;
+  auto evaluated_labels = EvaluateFunction("LABELS", v).ValueList();
+  labels.reserve(evaluated_labels.size());
+  for (auto label : evaluated_labels) {
+    labels.emplace_back(label.ValueString());
+  }
+  ASSERT_THAT(labels, UnorderedElementsAre("label1"));
+  ASSERT_THROW(EvaluateFunction("LABELS", 2), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, Range) {
+  EXPECT_THROW(EvaluateFunction("RANGE"), FunctionRuntimeException);
+  EXPECT_TRUE(EvaluateFunction("RANGE", 1, 2, TypedValue()).IsNull());
+  EXPECT_THROW(EvaluateFunction("RANGE", 1, TypedValue(), 1.3), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("RANGE", 1, 2, 0), FunctionRuntimeException);
+  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 1, 3)), ElementsAre(1, 2, 3));
+  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", -1, 5, 2)), ElementsAre(-1, 1, 3, 5));
+  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 2, 10, 3)), ElementsAre(2, 5, 8));
+  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 2, 2, 2)), ElementsAre(2));
+  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 3, 0, 5)), ElementsAre());
+  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 5, 1, -2)), ElementsAre(5, 3, 1));
+  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 6, 1, -2)), ElementsAre(6, 4, 2));
+  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 2, 2, -3)), ElementsAre(2));
+  EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", -2, 4, -1)), ElementsAre());
+}
+
+TEST_F(FunctionTest, Tail) {
+  ASSERT_THROW(EvaluateFunction("TAIL"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("TAIL", TypedValue()).IsNull());
+  auto argument = MakeTypedValueList();
+  ASSERT_EQ(EvaluateFunction("TAIL", argument).ValueList().size(), 0U);
+  argument = MakeTypedValueList(3, 4, true, "john");
+  auto list = EvaluateFunction("TAIL", argument).ValueList();
+  ASSERT_EQ(list.size(), 3U);
+  ASSERT_EQ(list[0].ValueInt(), 4);
+  ASSERT_EQ(list[1].ValueBool(), true);
+  ASSERT_EQ(list[2].ValueString(), "john");
+  ASSERT_THROW(EvaluateFunction("TAIL", 2), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, UniformSample) {
+  ASSERT_THROW(EvaluateFunction("UNIFORMSAMPLE"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("UNIFORMSAMPLE", TypedValue(), TypedValue()).IsNull());
+  ASSERT_TRUE(EvaluateFunction("UNIFORMSAMPLE", TypedValue(), 1).IsNull());
+  ASSERT_TRUE(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(), TypedValue()).IsNull());
+  ASSERT_TRUE(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(), 1).IsNull());
+  ASSERT_THROW(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), -1), FunctionRuntimeException);
+  ASSERT_EQ(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 0).ValueList().size(), 0);
+  ASSERT_EQ(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 2).ValueList().size(), 2);
+  ASSERT_EQ(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 3).ValueList().size(), 3);
+  ASSERT_EQ(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 5).ValueList().size(), 5);
+}
+
+TEST_F(FunctionTest, Abs) {
+  ASSERT_THROW(EvaluateFunction("ABS"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("ABS", TypedValue()).IsNull());
+  ASSERT_EQ(EvaluateFunction("ABS", -2).ValueInt(), 2);
+  ASSERT_EQ(EvaluateFunction("ABS", -2.5).ValueDouble(), 2.5);
+  ASSERT_THROW(EvaluateFunction("ABS", true), FunctionRuntimeException);
+}
+
+//// Test if log works. If it does then all functions wrapped with
+//// WRAP_CMATH_FLOAT_FUNCTION macro should work and are not gonna be tested for
+//// correctnes..
+TEST_F(FunctionTest, Log) {
+  ASSERT_THROW(EvaluateFunction("LOG"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("LOG", TypedValue()).IsNull());
+  ASSERT_DOUBLE_EQ(EvaluateFunction("LOG", 2).ValueDouble(), log(2));
+  ASSERT_DOUBLE_EQ(EvaluateFunction("LOG", 1.5).ValueDouble(), log(1.5));
+  // Not portable, but should work on most platforms.
+  ASSERT_TRUE(std::isnan(EvaluateFunction("LOG", -1.5).ValueDouble()));
+  ASSERT_THROW(EvaluateFunction("LOG", true), FunctionRuntimeException);
+}
+
+//// Function Round wraps round from cmath and will work if FunctionTest.Log test
+//// passes. This test is used to show behavior of round since it differs from
+//// neo4j's round.
+TEST_F(FunctionTest, Round) {
+  ASSERT_THROW(EvaluateFunction("ROUND"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("ROUND", TypedValue()).IsNull());
+  ASSERT_EQ(EvaluateFunction("ROUND", -2).ValueDouble(), -2);
+  ASSERT_EQ(EvaluateFunction("ROUND", -2.4).ValueDouble(), -2);
+  ASSERT_EQ(EvaluateFunction("ROUND", -2.5).ValueDouble(), -3);
+  ASSERT_EQ(EvaluateFunction("ROUND", -2.6).ValueDouble(), -3);
+  ASSERT_EQ(EvaluateFunction("ROUND", 2.4).ValueDouble(), 2);
+  ASSERT_EQ(EvaluateFunction("ROUND", 2.5).ValueDouble(), 3);
+  ASSERT_EQ(EvaluateFunction("ROUND", 2.6).ValueDouble(), 3);
+  ASSERT_THROW(EvaluateFunction("ROUND", true), FunctionRuntimeException);
+}
+
+// Check if wrapped functions are callable (check if everything was spelled
+// correctly...). Wrapper correctnes is checked in FunctionTest.Log function
+// test.
+TEST_F(FunctionTest, WrappedMathFunctions) {
+  for (auto function_name :
+       {"FLOOR", "CEIL", "ROUND", "EXP", "LOG", "LOG10", "SQRT", "ACOS", "ASIN", "ATAN", "COS", "SIN", "TAN"}) {
+    EvaluateFunction(function_name, 0.5);
+  }
+}
+
+TEST_F(FunctionTest, Atan2) {
+  ASSERT_THROW(EvaluateFunction("ATAN2"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("ATAN2", TypedValue(), 1).IsNull());
+  ASSERT_TRUE(EvaluateFunction("ATAN2", 1, TypedValue()).IsNull());
+  ASSERT_DOUBLE_EQ(EvaluateFunction("ATAN2", 2, -1.0).ValueDouble(), atan2(2, -1));
+  ASSERT_THROW(EvaluateFunction("ATAN2", 3.0, true), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, Sign) {
+  ASSERT_THROW(EvaluateFunction("SIGN"), FunctionRuntimeException);
+  ASSERT_TRUE(EvaluateFunction("SIGN", TypedValue()).IsNull());
+  ASSERT_EQ(EvaluateFunction("SIGN", -2).ValueInt(), -1);
+  ASSERT_EQ(EvaluateFunction("SIGN", -0.2).ValueInt(), -1);
+  ASSERT_EQ(EvaluateFunction("SIGN", 0.0).ValueInt(), 0);
+  ASSERT_EQ(EvaluateFunction("SIGN", 2.5).ValueInt(), 1);
+  ASSERT_THROW(EvaluateFunction("SIGN", true), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, E) {
+  ASSERT_THROW(EvaluateFunction("E", 1), FunctionRuntimeException);
+  ASSERT_DOUBLE_EQ(EvaluateFunction("E").ValueDouble(), M_E);
+}
+
+TEST_F(FunctionTest, Pi) {
+  ASSERT_THROW(EvaluateFunction("PI", 1), FunctionRuntimeException);
+  ASSERT_DOUBLE_EQ(EvaluateFunction("PI").ValueDouble(), M_PI);
+}
+
+TEST_F(FunctionTest, Rand) {
+  ASSERT_THROW(EvaluateFunction("RAND", 1), FunctionRuntimeException);
+  ASSERT_GE(EvaluateFunction("RAND").ValueDouble(), 0.0);
+  ASSERT_LT(EvaluateFunction("RAND").ValueDouble(), 1.0);
+}
+
+using memgraph::functions::kStartsWith;
+TEST_F(FunctionTest, StartsWith) {
+  EXPECT_THROW(EvaluateFunction(kStartsWith), FunctionRuntimeException);
+  EXPECT_TRUE(EvaluateFunction(kStartsWith, "a", TypedValue()).IsNull());
+  EXPECT_THROW(EvaluateFunction(kStartsWith, TypedValue(), 1.3), FunctionRuntimeException);
+  EXPECT_TRUE(EvaluateFunction(kStartsWith, "abc", "abc").ValueBool());
+  EXPECT_TRUE(EvaluateFunction(kStartsWith, "abcdef", "abc").ValueBool());
+  EXPECT_FALSE(EvaluateFunction(kStartsWith, "abcdef", "aBc").ValueBool());
+  EXPECT_FALSE(EvaluateFunction(kStartsWith, "abc", "abcd").ValueBool());
+}
+
+using memgraph::functions::kEndsWith;
+TEST_F(FunctionTest, EndsWith) {
+  EXPECT_THROW(EvaluateFunction(kEndsWith), FunctionRuntimeException);
+  EXPECT_TRUE(EvaluateFunction(kEndsWith, "a", TypedValue()).IsNull());
+  EXPECT_THROW(EvaluateFunction(kEndsWith, TypedValue(), 1.3), FunctionRuntimeException);
+  EXPECT_TRUE(EvaluateFunction(kEndsWith, "abc", "abc").ValueBool());
+  EXPECT_TRUE(EvaluateFunction(kEndsWith, "abcdef", "def").ValueBool());
+  EXPECT_FALSE(EvaluateFunction(kEndsWith, "abcdef", "dEf").ValueBool());
+  EXPECT_FALSE(EvaluateFunction(kEndsWith, "bcd", "abcd").ValueBool());
+}
+
+using memgraph::functions::kContains;
+TEST_F(FunctionTest, Contains) {
+  EXPECT_THROW(EvaluateFunction(kContains), FunctionRuntimeException);
+  EXPECT_TRUE(EvaluateFunction(kContains, "a", TypedValue()).IsNull());
+  EXPECT_THROW(EvaluateFunction(kContains, TypedValue(), 1.3), FunctionRuntimeException);
+  EXPECT_TRUE(EvaluateFunction(kContains, "abc", "abc").ValueBool());
+  EXPECT_TRUE(EvaluateFunction(kContains, "abcde", "bcd").ValueBool());
+  EXPECT_FALSE(EvaluateFunction(kContains, "cde", "abcdef").ValueBool());
+  EXPECT_FALSE(EvaluateFunction(kContains, "abcdef", "dEf").ValueBool());
+}
+
+TEST_F(FunctionTest, Assert) {
+  // Invalid calls.
+  ASSERT_THROW(EvaluateFunction("ASSERT"), FunctionRuntimeException);
+  ASSERT_THROW(EvaluateFunction("ASSERT", false, false), FunctionRuntimeException);
+  ASSERT_THROW(EvaluateFunction("ASSERT", "string", false), FunctionRuntimeException);
+  ASSERT_THROW(EvaluateFunction("ASSERT", false, "reason", true), FunctionRuntimeException);
+
+  // Valid calls, assertion fails.
+  ASSERT_THROW(EvaluateFunction("ASSERT", false), FunctionRuntimeException);
+  ASSERT_THROW(EvaluateFunction("ASSERT", false, "message"), FunctionRuntimeException);
+  try {
+    EvaluateFunction("ASSERT", false, "bbgba");
+  } catch (FunctionRuntimeException &e) {
+    ASSERT_TRUE(std::string(e.what()).find("bbgba") != std::string::npos);
+  }
+
+  // Valid calls, assertion passes.
+  ASSERT_TRUE(EvaluateFunction("ASSERT", true).ValueBool());
+  ASSERT_TRUE(EvaluateFunction("ASSERT", true, "message").ValueBool());
+}
+
+TEST_F(FunctionTest, Counter) {
+  EXPECT_THROW(EvaluateFunction("COUNTER"), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("COUNTER", "a"), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("COUNTER", "a", "b"), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("COUNTER", "a", "b", "c"), FunctionRuntimeException);
+
+  EXPECT_EQ(EvaluateFunction("COUNTER", "c1", 0).ValueInt(), 0);
+  EXPECT_EQ(EvaluateFunction("COUNTER", "c1", 0).ValueInt(), 1);
+  EXPECT_EQ(EvaluateFunction("COUNTER", "c2", 0).ValueInt(), 0);
+  EXPECT_EQ(EvaluateFunction("COUNTER", "c1", 0).ValueInt(), 2);
+  EXPECT_EQ(EvaluateFunction("COUNTER", "c2", 0).ValueInt(), 1);
+
+  EXPECT_EQ(EvaluateFunction("COUNTER", "c3", -1).ValueInt(), -1);
+  EXPECT_EQ(EvaluateFunction("COUNTER", "c3", -1).ValueInt(), 0);
+  EXPECT_EQ(EvaluateFunction("COUNTER", "c3", -1).ValueInt(), 1);
+
+  EXPECT_EQ(EvaluateFunction("COUNTER", "c4", 0, 5).ValueInt(), 0);
+  EXPECT_EQ(EvaluateFunction("COUNTER", "c4", 0, 5).ValueInt(), 5);
+  EXPECT_EQ(EvaluateFunction("COUNTER", "c4", 0, 5).ValueInt(), 10);
+
+  EXPECT_EQ(EvaluateFunction("COUNTER", "c5", 0, -5).ValueInt(), 0);
+  EXPECT_EQ(EvaluateFunction("COUNTER", "c5", 0, -5).ValueInt(), -5);
+  EXPECT_EQ(EvaluateFunction("COUNTER", "c5", 0, -5).ValueInt(), -10);
+
+  EXPECT_THROW(EvaluateFunction("COUNTER", "c6", 0, 0), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, Id) {
+  auto v = CreateVertex({}, shard_manager.get());
+  EXPECT_THROW(EvaluateFunction("ID", v), FunctionRuntimeException);
+  auto e = CreateEdge({}, shard_manager.get(), EdgeId{10});
+  EXPECT_EQ(EvaluateFunction("ID", e).ValueInt(), 10);
+}
+
+TEST_F(FunctionTest, ToStringNull) { EXPECT_TRUE(EvaluateFunction("TOSTRING", TypedValue()).IsNull()); }
+
+TEST_F(FunctionTest, ToStringString) {
+  EXPECT_EQ(EvaluateFunction("TOSTRING", "").ValueString(), "");
+  EXPECT_EQ(EvaluateFunction("TOSTRING", "this is a string").ValueString(), "this is a string");
+}
+
+TEST_F(FunctionTest, ToStringInteger) {
+  EXPECT_EQ(EvaluateFunction("TOSTRING", -23321312).ValueString(), "-23321312");
+  EXPECT_EQ(EvaluateFunction("TOSTRING", 0).ValueString(), "0");
+  EXPECT_EQ(EvaluateFunction("TOSTRING", 42).ValueString(), "42");
+}
+
+TEST_F(FunctionTest, ToStringDouble) {
+  EXPECT_EQ(EvaluateFunction("TOSTRING", -42.42).ValueString(), "-42.420000");
+  EXPECT_EQ(EvaluateFunction("TOSTRING", 0.0).ValueString(), "0.000000");
+  EXPECT_EQ(EvaluateFunction("TOSTRING", 238910.2313217).ValueString(), "238910.231322");
+}
+
+TEST_F(FunctionTest, ToStringBool) {
+  EXPECT_EQ(EvaluateFunction("TOSTRING", true).ValueString(), "true");
+  EXPECT_EQ(EvaluateFunction("TOSTRING", false).ValueString(), "false");
+}
+
+TEST_F(FunctionTest, ToStringExceptions) {
+  EXPECT_THROW(EvaluateFunction("TOSTRING", 1, 2, 3), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, Left) {
+  EXPECT_THROW(EvaluateFunction("LEFT"), FunctionRuntimeException);
+
+  EXPECT_TRUE(EvaluateFunction("LEFT", TypedValue(), TypedValue()).IsNull());
+  EXPECT_TRUE(EvaluateFunction("LEFT", TypedValue(), 10).IsNull());
+  EXPECT_THROW(EvaluateFunction("LEFT", TypedValue(), -10), FunctionRuntimeException);
+
+  EXPECT_EQ(EvaluateFunction("LEFT", "memgraph", 0).ValueString(), "");
+  EXPECT_EQ(EvaluateFunction("LEFT", "memgraph", 3).ValueString(), "mem");
+  EXPECT_EQ(EvaluateFunction("LEFT", "memgraph", 1000).ValueString(), "memgraph");
+  EXPECT_THROW(EvaluateFunction("LEFT", "memgraph", -10), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("LEFT", "memgraph", "graph"), FunctionRuntimeException);
+
+  EXPECT_THROW(EvaluateFunction("LEFT", 132, 10), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, Right) {
+  EXPECT_THROW(EvaluateFunction("RIGHT"), FunctionRuntimeException);
+
+  EXPECT_TRUE(EvaluateFunction("RIGHT", TypedValue(), TypedValue()).IsNull());
+  EXPECT_TRUE(EvaluateFunction("RIGHT", TypedValue(), 10).IsNull());
+  EXPECT_THROW(EvaluateFunction("RIGHT", TypedValue(), -10), FunctionRuntimeException);
+
+  EXPECT_EQ(EvaluateFunction("RIGHT", "memgraph", 0).ValueString(), "");
+  EXPECT_EQ(EvaluateFunction("RIGHT", "memgraph", 3).ValueString(), "aph");
+  EXPECT_EQ(EvaluateFunction("RIGHT", "memgraph", 1000).ValueString(), "memgraph");
+  EXPECT_THROW(EvaluateFunction("RIGHT", "memgraph", -10), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("RIGHT", "memgraph", "graph"), FunctionRuntimeException);
+
+  EXPECT_THROW(EvaluateFunction("RIGHT", 132, 10), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, Trimming) {
+  EXPECT_TRUE(EvaluateFunction("LTRIM", TypedValue()).IsNull());
+  EXPECT_TRUE(EvaluateFunction("RTRIM", TypedValue()).IsNull());
+  EXPECT_TRUE(EvaluateFunction("TRIM", TypedValue()).IsNull());
+
+  EXPECT_EQ(EvaluateFunction("LTRIM", "  abc    ").ValueString(), "abc    ");
+  EXPECT_EQ(EvaluateFunction("RTRIM", " abc ").ValueString(), " abc");
+  EXPECT_EQ(EvaluateFunction("TRIM", "abc").ValueString(), "abc");
+
+  EXPECT_THROW(EvaluateFunction("LTRIM", "x", "y"), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("RTRIM", "x", "y"), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("TRIM", "x", "y"), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, Reverse) {
+  EXPECT_TRUE(EvaluateFunction("REVERSE", TypedValue()).IsNull());
+  EXPECT_EQ(EvaluateFunction("REVERSE", "abc").ValueString(), "cba");
+  EXPECT_THROW(EvaluateFunction("REVERSE", "x", "y"), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, Replace) {
+  EXPECT_THROW(EvaluateFunction("REPLACE"), FunctionRuntimeException);
+  EXPECT_TRUE(EvaluateFunction("REPLACE", TypedValue(), "l", "w").IsNull());
+  EXPECT_TRUE(EvaluateFunction("REPLACE", "hello", TypedValue(), "w").IsNull());
+  EXPECT_TRUE(EvaluateFunction("REPLACE", "hello", "l", TypedValue()).IsNull());
+  EXPECT_EQ(EvaluateFunction("REPLACE", "hello", "l", "w").ValueString(), "hewwo");
+
+  EXPECT_THROW(EvaluateFunction("REPLACE", 1, "l", "w"), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("REPLACE", "hello", 1, "w"), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("REPLACE", "hello", "l", 1), FunctionRuntimeException);
+}
+
+TEST_F(FunctionTest, Split) {
+  EXPECT_THROW(EvaluateFunction("SPLIT"), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("SPLIT", "one,two", 1), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("SPLIT", 1, "one,two"), FunctionRuntimeException);
+
+  EXPECT_TRUE(EvaluateFunction("SPLIT", TypedValue(), TypedValue()).IsNull());
+  EXPECT_TRUE(EvaluateFunction("SPLIT", "one,two", TypedValue()).IsNull());
+  EXPECT_TRUE(EvaluateFunction("SPLIT", TypedValue(), ",").IsNull());
+
+  auto result = EvaluateFunction("SPLIT", "one,two", ",");
+  EXPECT_TRUE(result.IsList());
+  EXPECT_EQ(result.ValueList()[0].ValueString(), "one");
+  EXPECT_EQ(result.ValueList()[1].ValueString(), "two");
+}
+
+TEST_F(FunctionTest, Substring) {
+  EXPECT_THROW(EvaluateFunction("SUBSTRING"), FunctionRuntimeException);
+
+  EXPECT_TRUE(EvaluateFunction("SUBSTRING", TypedValue(), 0, 10).IsNull());
+  EXPECT_THROW(EvaluateFunction("SUBSTRING", TypedValue(), TypedValue()), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("SUBSTRING", TypedValue(), -10), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("SUBSTRING", TypedValue(), 0, TypedValue()), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("SUBSTRING", TypedValue(), 0, -10), FunctionRuntimeException);
+
+  EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 2).ValueString(), "llo");
+  EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 10).ValueString(), "");
+  EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 2, 0).ValueString(), "");
+  EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 1, 3).ValueString(), "ell");
+  EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 1, 4).ValueString(), "ello");
+  EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 1, 10).ValueString(), "ello");
+}
+
+TEST_F(FunctionTest, ToLower) {
+  EXPECT_THROW(EvaluateFunction("TOLOWER"), FunctionRuntimeException);
+  EXPECT_TRUE(EvaluateFunction("TOLOWER", TypedValue()).IsNull());
+  EXPECT_EQ(EvaluateFunction("TOLOWER", "Ab__C").ValueString(), "ab__c");
+}
+
+TEST_F(FunctionTest, ToUpper) {
+  EXPECT_THROW(EvaluateFunction("TOUPPER"), FunctionRuntimeException);
+  EXPECT_TRUE(EvaluateFunction("TOUPPER", TypedValue()).IsNull());
+  EXPECT_EQ(EvaluateFunction("TOUPPER", "Ab__C").ValueString(), "AB__C");
+}
+
+TEST_F(FunctionTest, ToByteString) {
+  EXPECT_THROW(EvaluateFunction("TOBYTESTRING"), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("TOBYTESTRING", 42), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("TOBYTESTRING", TypedValue()), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("TOBYTESTRING", "", 42), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("TOBYTESTRING", "ff"), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("TOBYTESTRING", "00"), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("TOBYTESTRING", "0xG"), FunctionRuntimeException);
+  EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "").ValueString(), "");
+  EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0x").ValueString(), "");
+  EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0X").ValueString(), "");
+  EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0x0123456789aAbBcCdDeEfF").ValueString(),
+            "\x01\x23\x45\x67\x89\xAA\xBB\xCC\xDD\xEE\xFF");
+  EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0x042").ValueString().size(), 2);
+  EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0x042").ValueString(),
+            memgraph::utils::pmr::string("\x00\x42", 2, memgraph::utils::NewDeleteResource()));
+}
+
+TEST_F(FunctionTest, FromByteString) {
+  EXPECT_THROW(EvaluateFunction("FROMBYTESTRING"), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("FROMBYTESTRING", 42), FunctionRuntimeException);
+  EXPECT_THROW(EvaluateFunction("FROMBYTESTRING", TypedValue()), FunctionRuntimeException);
+  EXPECT_EQ(EvaluateFunction("FROMBYTESTRING", "").ValueString(), "");
+  auto bytestring = EvaluateFunction("TOBYTESTRING", "0x123456789aAbBcCdDeEfF");
+  EXPECT_EQ(EvaluateFunction("FROMBYTESTRING", bytestring).ValueString(), "0x0123456789aabbccddeeff");
+  EXPECT_EQ(EvaluateFunction("FROMBYTESTRING", std::string("\x00\x42", 2)).ValueString(), "0x0042");
+}
+
+}  // namespace memgraph::query::v2::tests
diff --git a/tests/unit/storage_v3_edge.cpp b/tests/unit/storage_v3_edge.cpp
index 64f7f4e2e..3d2ab8bbd 100644
--- a/tests/unit/storage_v3_edge.cpp
+++ b/tests/unit/storage_v3_edge.cpp
@@ -97,8 +97,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
     auto edge = res.GetValue();
     ASSERT_EQ(edge.EdgeType(), et);
     ASSERT_EQ(edge.Gid(), edge_id);
-    ASSERT_EQ(edge.FromVertex(), from_id);
-    ASSERT_EQ(edge.ToVertex(), to_id);
+    ASSERT_EQ(edge.From(), from_id);
+    ASSERT_EQ(edge.To(), to_id);
 
     // Check edges without filters
     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
@@ -116,8 +116,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0);
     ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0);
@@ -130,8 +130,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
@@ -187,8 +187,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     {
       auto ret = vertex_from->OutEdges(View::NEW);
@@ -199,8 +199,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     {
       auto ret = vertex_to->InEdges(View::OLD);
@@ -211,8 +211,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     {
       auto ret = vertex_to->InEdges(View::NEW);
@@ -223,8 +223,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
@@ -278,592 +278,6 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) {
   }
 }
 
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}});
-//   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-//   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-
-//   // Create vertices
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_to = acc.CreateVertex();
-//     auto vertex_from = acc.CreateVertex();
-//     gid_to = vertex_to.Gid();
-//     gid_from = vertex_from.Gid();
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Create edge
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto res = acc.CreateEdge(&from_id, &to_id, et);
-//     ASSERT_TRUE(res.HasValue());
-//     auto edge = res.GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), *vertex_from);
-//     ASSERT_EQ(edge.ToVertex(), *vertex_to);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex_from->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex_to->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex_from->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_from->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_to->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_to->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-// }
-
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}});
-//   memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-
-//   // Create vertex
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.CreateVertex();
-//     gid_vertex = vertex.Gid();
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Create edge
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto res = acc.CreateEdge(&*vertex, &*vertex, et);
-//     ASSERT_TRUE(res.HasValue());
-//     auto edge = res.GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), *vertex);
-//     ASSERT_EQ(edge.ToVertex(), *vertex);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->InDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->OutDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     // Check edges without filters
-//     {
-//       auto ret = vertex->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-// }
-
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}});
-//   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-//   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-
-//   // Create vertices
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.CreateVertex();
-//     auto vertex_to = acc.CreateVertex();
-//     gid_from = vertex_from.Gid();
-//     gid_to = vertex_to.Gid();
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Create edge, but abort the transaction
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto res = acc.CreateEdge(&from_id, &to_id, et);
-//     ASSERT_TRUE(res.HasValue());
-//     auto edge = res.GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), *vertex_from);
-//     ASSERT_EQ(edge.ToVertex(), *vertex_to);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex_from->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex_to->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0);
-
-//     acc.Abort();
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Create edge
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto res = acc.CreateEdge(&from_id, &to_id, et);
-//     ASSERT_TRUE(res.HasValue());
-//     auto edge = res.GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), *vertex_from);
-//     ASSERT_EQ(edge.ToVertex(), *vertex_to);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex_from->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex_to->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex_from->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_from->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_to->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_to->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-// }
-
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
   // Create vertices
@@ -900,8 +314,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
     auto edge = res.GetValue();
     ASSERT_EQ(edge.EdgeType(), et);
     ASSERT_EQ(edge.Gid(), edge_id);
-    ASSERT_EQ(edge.FromVertex(), from_id);
-    ASSERT_EQ(edge.ToVertex(), to_id);
+    ASSERT_EQ(edge.From(), from_id);
+    ASSERT_EQ(edge.To(), to_id);
 
     // Check edges without filters
     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
@@ -919,8 +333,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0);
     ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0);
@@ -933,8 +347,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
@@ -1012,8 +426,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
     auto edge = res.GetValue();
     ASSERT_EQ(edge.EdgeType(), et);
     ASSERT_EQ(edge.Gid(), edge_id);
-    ASSERT_EQ(edge.FromVertex(), from_id);
-    ASSERT_EQ(edge.ToVertex(), to_id);
+    ASSERT_EQ(edge.From(), from_id);
+    ASSERT_EQ(edge.To(), to_id);
 
     // Check edges without filters
     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
@@ -1031,8 +445,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0);
     ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0);
@@ -1045,8 +459,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
@@ -1104,8 +518,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     {
       auto ret = vertex_from->OutEdges(View::NEW);
@@ -1116,8 +530,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     {
       auto ret = vertex_to->InEdges(View::OLD);
@@ -1128,8 +542,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     {
       auto ret = vertex_to->InEdges(View::NEW);
@@ -1140,8 +554,8 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
@@ -1173,232 +587,6 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) {
   }
 }
 
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}});
-//   memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-
-//   // Create vertex
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.CreateVertex();
-//     gid_vertex = vertex.Gid();
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Create edge, but abort the transaction
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto res = acc.CreateEdge(&*vertex, &*vertex, et);
-//     ASSERT_TRUE(res.HasValue());
-//     auto edge = res.GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), *vertex);
-//     ASSERT_EQ(edge.ToVertex(), *vertex);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->InDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->OutDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-
-//     acc.Abort();
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex->OutDegree(View::NEW), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Create edge
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto res = acc.CreateEdge(&*vertex, &*vertex, et);
-//     ASSERT_TRUE(res.HasValue());
-//     auto edge = res.GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), *vertex);
-//     ASSERT_EQ(edge.ToVertex(), *vertex);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->InDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->OutDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     // Check edges without filters
-//     {
-//       auto ret = vertex->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-// }
-
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
   // Create vertex
@@ -1432,8 +620,8 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
     auto edge = res.GetValue();
     ASSERT_EQ(edge.EdgeType(), et);
     ASSERT_EQ(edge.Gid(), edge_id);
-    ASSERT_EQ(edge.FromVertex(), from_id);
-    ASSERT_EQ(edge.ToVertex(), to_id);
+    ASSERT_EQ(edge.From(), from_id);
+    ASSERT_EQ(edge.To(), to_id);
 
     // Check edges without filters
     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
@@ -1451,8 +639,8 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
 
     // Check edges with filters
@@ -1490,8 +678,8 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     {
       auto ret = vertex_from->OutEdges(View::NEW);
@@ -1502,8 +690,8 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
 
     // Check edges with filters
@@ -1530,7 +718,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
 
     const auto edge = vertex_from->OutEdges(View::NEW).GetValue()[0];
 
-    const auto res = acc.DeleteEdge(edge.FromVertex(), edge.ToVertex(), edge.Gid());
+    const auto res = acc.DeleteEdge(edge.From(), edge.To(), edge.Gid());
     ASSERT_TRUE(res.HasValue());
     ASSERT_TRUE(res.GetValue());
 
@@ -1548,8 +736,8 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0);
     ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0);
@@ -1589,921 +777,6 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) {
   }
 }
 
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}});
-//   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-//   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-
-//   // Create vertices
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_to = acc.CreateVertex();
-//     auto vertex_from = acc.CreateVertex();
-//     gid_from = vertex_from.Gid();
-//     gid_to = vertex_to.Gid();
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Create edge
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto res = acc.CreateEdge(&from_id, &to_id, et);
-//     ASSERT_TRUE(res.HasValue());
-//     auto edge = res.GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), *vertex_from);
-//     ASSERT_EQ(edge.ToVertex(), *vertex_to);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex_from->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex_to->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex_from->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_from->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_to->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_to->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Delete edge
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto edge = vertex_from->OutEdges(View::NEW).GetValue()[0];
-
-//     auto res = acc.DeleteEdge(&edge);
-//     ASSERT_TRUE(res.HasValue());
-//     ASSERT_TRUE(res.GetValue());
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex_from->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex_to->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-// }
-
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}});
-//   memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-
-//   // Create vertex
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.CreateVertex();
-//     gid_vertex = vertex.Gid();
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Create edge
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto res = acc.CreateEdge(&*vertex, &*vertex, et);
-//     ASSERT_TRUE(res.HasValue());
-//     auto edge = res.GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), *vertex);
-//     ASSERT_EQ(edge.ToVertex(), *vertex);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->InDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->OutDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     // Check edges without filters
-//     {
-//       auto ret = vertex->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Delete edge
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto res = acc.DeleteEdge(&edge);
-//     ASSERT_TRUE(res.HasValue());
-//     ASSERT_TRUE(res.GetValue());
-
-//     // Check edges without filters
-//     {
-//       auto ret = vertex->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     ASSERT_EQ(vertex->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex->InDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     ASSERT_EQ(vertex->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et}, &*vertex)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex->OutDegree(View::NEW), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-// }
-
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}});
-//   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-//   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-
-//   // Create vertices
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.CreateVertex();
-//     auto vertex_to = acc.CreateVertex();
-//     gid_from = vertex_from.Gid();
-//     gid_to = vertex_to.Gid();
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Create edge
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto res = acc.CreateEdge(&from_id, &to_id, et);
-//     ASSERT_TRUE(res.HasValue());
-//     auto edge = res.GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), *vertex_from);
-//     ASSERT_EQ(edge.ToVertex(), *vertex_to);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex_from->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex_to->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex_from->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_from->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_to->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_to->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Delete the edge, but abort the transaction
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto edge = vertex_from->OutEdges(View::NEW).GetValue()[0];
-
-//     auto res = acc.DeleteEdge(&edge);
-//     ASSERT_TRUE(res.HasValue());
-//     ASSERT_TRUE(res.GetValue());
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex_from->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex_to->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0);
-
-//     acc.Abort();
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex_from->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_from->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_to->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_to->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Delete the edge
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto edge = vertex_from->OutEdges(View::NEW).GetValue()[0];
-
-//     auto res = acc.DeleteEdge(&edge);
-//     ASSERT_TRUE(res.HasValue());
-//     ASSERT_TRUE(res.GetValue());
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex_from->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex_to->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-// }
-
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
   // Create vertex
@@ -2532,8 +805,8 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
     auto edge = res.GetValue();
     ASSERT_EQ(edge.EdgeType(), et);
     ASSERT_EQ(edge.Gid(), edge_id);
-    ASSERT_EQ(edge.FromVertex(), from_id);
-    ASSERT_EQ(edge.ToVertex(), to_id);
+    ASSERT_EQ(edge.From(), from_id);
+    ASSERT_EQ(edge.To(), to_id);
 
     // Check edges without filters
     ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0);
@@ -2547,8 +820,8 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
@@ -2585,8 +858,8 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     {
       auto ret = vertex_to->InEdges(View::NEW);
@@ -2597,8 +870,8 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
@@ -2638,7 +911,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 
     const auto edge = vertex_to->InEdges(View::NEW).GetValue()[0];
 
-    auto res = acc.DeleteEdge(edge.FromVertex(), edge.ToVertex(), edge.Gid());
+    auto res = acc.DeleteEdge(edge.From(), edge.To(), edge.Gid());
     ASSERT_TRUE(res.HasValue());
     ASSERT_TRUE(res.GetValue());
 
@@ -2653,8 +926,8 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0);
     ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0);
@@ -2692,8 +965,8 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     {
       auto ret = vertex_to->InEdges(View::NEW);
@@ -2704,8 +977,8 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
@@ -2745,7 +1018,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
 
     const auto edge = vertex_to->InEdges(View::NEW).GetValue()[0];
 
-    auto res = acc.DeleteEdge(edge.FromVertex(), edge.ToVertex(), edge.Gid());
+    auto res = acc.DeleteEdge(edge.From(), edge.To(), edge.Gid());
     ASSERT_TRUE(res.HasValue());
     ASSERT_TRUE(res.GetValue());
 
@@ -2758,8 +1031,8 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
       ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1);
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0);
     ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0);
@@ -2813,365 +1086,6 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) {
   }
 }
 
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}});
-//   memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-
-//   // Create vertex
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.CreateVertex();
-//     gid_vertex = vertex.Gid();
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Create edge
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto res = acc.CreateEdge(&*vertex, &*vertex, et);
-//     ASSERT_TRUE(res.HasValue());
-//     auto edge = res.GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), *vertex);
-//     ASSERT_EQ(edge.ToVertex(), *vertex);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->InDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->OutDegree(View::OLD), 0);
-//     {
-//       auto ret = vertex->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     // Check edges without filters
-//     {
-//       auto ret = vertex->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Delete the edge, but abort the transaction
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto res = acc.DeleteEdge(&edge);
-//     ASSERT_TRUE(res.HasValue());
-//     ASSERT_TRUE(res.GetValue());
-
-//     // Check edges without filters
-//     {
-//       auto ret = vertex->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     ASSERT_EQ(vertex->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex->InDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     ASSERT_EQ(vertex->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et}, &*vertex)->size(), 0);
-
-//     acc.Abort();
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     // Check edges without filters
-//     {
-//       auto ret = vertex->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     {
-//       auto ret = vertex->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Delete the edge
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto res = acc.DeleteEdge(&edge);
-//     ASSERT_TRUE(res.HasValue());
-//     ASSERT_TRUE(res.GetValue());
-
-//     // Check edges without filters
-//     {
-//       auto ret = vertex->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     ASSERT_EQ(vertex->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex->InDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex);
-//       ASSERT_EQ(e.ToVertex(), *vertex);
-//     }
-//     ASSERT_EQ(vertex->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex->OutDegree(View::NEW), 0);
-
-//     auto other_et = acc.NameToEdgeType("other");
-
-//     // Check edges with filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->InEdges(View::OLD, {other_et}, &*vertex)->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {}, &*vertex)->size(), 1);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et}, &*vertex)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check whether the edge exists
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid_vertex, View::NEW);
-//     ASSERT_TRUE(vertex);
-
-//     // Check edges without filters
-//     ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex->OutDegree(View::NEW), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-// }
-
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
   // Create vertices
@@ -3195,8 +1109,8 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
     auto edge = res.GetValue();
     ASSERT_EQ(edge.EdgeType(), et);
     ASSERT_EQ(edge.Gid(), edge_id);
-    ASSERT_EQ(edge.FromVertex(), from_id);
-    ASSERT_EQ(edge.ToVertex(), to_id);
+    ASSERT_EQ(edge.From(), from_id);
+    ASSERT_EQ(edge.To(), to_id);
 
     // Check edges
     ASSERT_EQ(vertex_from.InEdges(View::NEW)->size(), 0);
@@ -3210,8 +1124,8 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     {
       auto ret = vertex_to.InEdges(View::NEW);
@@ -3221,8 +1135,8 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
       ASSERT_EQ(*vertex_to.InDegree(View::NEW), 1);
       auto e = edges[0];
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to.OutEdges(View::NEW)->size(), 0);
     ASSERT_EQ(*vertex_to.OutDegree(View::NEW), 0);
@@ -3266,8 +1180,8 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_from->OutEdges(View::NEW).GetError(), SHARD_ERROR(ErrorCode::DELETED_OBJECT));
     ASSERT_EQ(vertex_from->OutDegree(View::NEW).GetError(), SHARD_ERROR(ErrorCode::DELETED_OBJECT));
@@ -3280,8 +1194,8 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
       auto e = edges[0];
       ASSERT_EQ(e.EdgeType(), et);
       ASSERT_EQ(e.Gid(), edge_id);
-      ASSERT_EQ(e.FromVertex(), from_id);
-      ASSERT_EQ(e.ToVertex(), to_id);
+      ASSERT_EQ(e.From(), from_id);
+      ASSERT_EQ(e.To(), to_id);
     }
     ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0);
     ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0);
@@ -3312,1964 +1226,4 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
   }
 }
-
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}});
-//   memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-//   memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-
-//   // Create dataset
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex1 = acc.CreateVertex();
-//     auto vertex2 = acc.CreateVertex();
-
-//     gid_vertex1 = vertex1.Gid();
-//     gid_vertex2 = vertex2.Gid();
-
-//     auto et1 = acc.NameToEdgeType("et1");
-//     auto et2 = acc.NameToEdgeType("et2");
-//     auto et3 = acc.NameToEdgeType("et3");
-//     auto et4 = acc.NameToEdgeType("et4");
-
-//     auto res1 = acc.CreateEdge(&vertex1, &vertex2, et1);
-//     ASSERT_TRUE(res1.HasValue());
-//     auto edge1 = res1.GetValue();
-//     ASSERT_EQ(edge1.EdgeType(), et1);
-//     ASSERT_EQ(edge1.FromVertex(), vertex1);
-//     ASSERT_EQ(edge1.ToVertex(), vertex2);
-
-//     auto res2 = acc.CreateEdge(&vertex2, &vertex1, et2);
-//     ASSERT_TRUE(res2.HasValue());
-//     auto edge2 = res2.GetValue();
-//     ASSERT_EQ(edge2.EdgeType(), et2);
-//     ASSERT_EQ(edge2.FromVertex(), vertex2);
-//     ASSERT_EQ(edge2.ToVertex(), vertex1);
-
-//     auto res3 = acc.CreateEdge(&vertex1, &vertex1, et3);
-//     ASSERT_TRUE(res3.HasValue());
-//     auto edge3 = res3.GetValue();
-//     ASSERT_EQ(edge3.EdgeType(), et3);
-//     ASSERT_EQ(edge3.FromVertex(), vertex1);
-//     ASSERT_EQ(edge3.ToVertex(), vertex1);
-
-//     auto res4 = acc.CreateEdge(&vertex2, &vertex2, et4);
-//     ASSERT_TRUE(res4.HasValue());
-//     auto edge4 = res4.GetValue();
-//     ASSERT_EQ(edge4.EdgeType(), et4);
-//     ASSERT_EQ(edge4.FromVertex(), vertex2);
-//     ASSERT_EQ(edge4.ToVertex(), vertex2);
-
-//     // Check edges
-//     {
-//       auto ret = vertex1.InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1.InDegree(View::NEW), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et2);
-//         ASSERT_EQ(e.FromVertex(), vertex2);
-//         ASSERT_EQ(e.ToVertex(), vertex1);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et3);
-//         ASSERT_EQ(e.FromVertex(), vertex1);
-//         ASSERT_EQ(e.ToVertex(), vertex1);
-//       }
-//     }
-//     {
-//       auto ret = vertex1.OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1.OutDegree(View::NEW), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et1);
-//         ASSERT_EQ(e.FromVertex(), vertex1);
-//         ASSERT_EQ(e.ToVertex(), vertex2);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et3);
-//         ASSERT_EQ(e.FromVertex(), vertex1);
-//         ASSERT_EQ(e.ToVertex(), vertex1);
-//       }
-//     }
-//     {
-//       auto ret = vertex2.InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2.InDegree(View::NEW), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et1);
-//         ASSERT_EQ(e.FromVertex(), vertex1);
-//         ASSERT_EQ(e.ToVertex(), vertex2);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et4);
-//         ASSERT_EQ(e.FromVertex(), vertex2);
-//         ASSERT_EQ(e.ToVertex(), vertex2);
-//       }
-//     }
-//     {
-//       auto ret = vertex2.OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2.OutDegree(View::NEW), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et2);
-//         ASSERT_EQ(e.FromVertex(), vertex2);
-//         ASSERT_EQ(e.ToVertex(), vertex1);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et4);
-//         ASSERT_EQ(e.FromVertex(), vertex2);
-//         ASSERT_EQ(e.ToVertex(), vertex2);
-//       }
-//     }
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Detach delete vertex
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex1 = acc.FindVertex(gid_vertex1, View::NEW);
-//     auto vertex2 = acc.FindVertex(gid_vertex2, View::NEW);
-//     ASSERT_TRUE(vertex1);
-//     ASSERT_TRUE(vertex2);
-
-//     auto et1 = acc.NameToEdgeType("et1");
-//     auto et2 = acc.NameToEdgeType("et2");
-//     auto et3 = acc.NameToEdgeType("et3");
-//     auto et4 = acc.NameToEdgeType("et4");
-
-//     // Delete must fail
-//     {
-//       auto ret = acc.DeleteVertex(&*vertex1);
-//       ASSERT_TRUE(ret.HasError());
-//       ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES);
-//     }
-
-//     // Detach delete vertex
-//     {
-//       auto ret = acc.DetachDeleteVertex(&*vertex1);
-//       ASSERT_TRUE(ret.HasValue());
-//       ASSERT_TRUE(*ret);
-//     }
-
-//     // Check edges
-//     {
-//       auto ret = vertex1->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->InDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et2);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et3);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//     }
-//     ASSERT_EQ(vertex1->InEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     ASSERT_EQ(vertex1->InDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     {
-//       auto ret = vertex1->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->OutDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et1);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et3);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//     }
-//     ASSERT_EQ(vertex1->OutEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     ASSERT_EQ(vertex1->OutDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     {
-//       auto ret = vertex2->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->InDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et1);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et4);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//     }
-//     {
-//       auto ret = vertex2->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex2->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et4);
-//       ASSERT_EQ(e.FromVertex(), *vertex2);
-//       ASSERT_EQ(e.ToVertex(), *vertex2);
-//     }
-//     {
-//       auto ret = vertex2->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->OutDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et2);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et4);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//     }
-//     {
-//       auto ret = vertex2->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex2->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et4);
-//       ASSERT_EQ(e.FromVertex(), *vertex2);
-//       ASSERT_EQ(e.ToVertex(), *vertex2);
-//     }
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check dataset
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex1 = acc.FindVertex(gid_vertex1, View::NEW);
-//     auto vertex2 = acc.FindVertex(gid_vertex2, View::NEW);
-//     ASSERT_FALSE(vertex1);
-//     ASSERT_TRUE(vertex2);
-
-//     auto et4 = acc.NameToEdgeType("et4");
-
-//     // Check edges
-//     {
-//       auto ret = vertex2->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex2->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et4);
-//       ASSERT_EQ(e.FromVertex(), *vertex2);
-//       ASSERT_EQ(e.ToVertex(), *vertex2);
-//     }
-//     {
-//       auto ret = vertex2->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex2->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et4);
-//       ASSERT_EQ(e.FromVertex(), *vertex2);
-//       ASSERT_EQ(e.ToVertex(), *vertex2);
-//     }
-//     {
-//       auto ret = vertex2->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex2->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et4);
-//       ASSERT_EQ(e.FromVertex(), *vertex2);
-//       ASSERT_EQ(e.ToVertex(), *vertex2);
-//     }
-//     {
-//       auto ret = vertex2->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex2->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et4);
-//       ASSERT_EQ(e.FromVertex(), *vertex2);
-//       ASSERT_EQ(e.ToVertex(), *vertex2);
-//     }
-//   }
-// }
-
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}});
-//   memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-//   memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-
-//   // Create dataset
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.CreateVertex();
-//     auto vertex_to = acc.CreateVertex();
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     auto res = acc.CreateEdge(&vertex_from, &vertex_to, et);
-//     ASSERT_TRUE(res.HasValue());
-//     auto edge = res.GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), vertex_from);
-//     ASSERT_EQ(edge.ToVertex(), vertex_to);
-
-//     gid_from = vertex_from.Gid();
-//     gid_to = vertex_to.Gid();
-
-//     // Check edges
-//     ASSERT_EQ(vertex_from.InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from.InDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex_from.OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from.OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), vertex_from);
-//       ASSERT_EQ(e.ToVertex(), vertex_to);
-//     }
-//     {
-//       auto ret = vertex_to.InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to.InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), vertex_from);
-//       ASSERT_EQ(e.ToVertex(), vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to.OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to.OutDegree(View::NEW), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Detach delete vertex, but abort the transaction
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     // Delete must fail
-//     {
-//       auto ret = acc.DeleteVertex(&from_id);
-//       ASSERT_TRUE(ret.HasError());
-//       ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES);
-//     }
-
-//     // Detach delete vertex
-//     {
-//       auto ret = acc.DetachDeleteVertex(&from_id);
-//       ASSERT_TRUE(ret.HasValue());
-//       ASSERT_TRUE(*ret);
-//     }
-
-//     // Check edges
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     ASSERT_EQ(vertex_from->InDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     {
-//       auto ret = vertex_from->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     ASSERT_EQ(vertex_from->OutDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     {
-//       auto ret = vertex_to->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     acc.Abort();
-//   }
-
-//   // Check dataset
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     // Check edges
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0);
-//     {
-//       auto ret = vertex_from->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     {
-//       auto ret = vertex_to->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Detach delete vertex
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_TRUE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     auto et = acc.NameToEdgeType("et5");
-
-//     // Delete must fail
-//     {
-//       auto ret = acc.DeleteVertex(&from_id);
-//       ASSERT_TRUE(ret.HasError());
-//       ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES);
-//     }
-
-//     // Detach delete vertex
-//     {
-//       auto ret = acc.DetachDeleteVertex(&from_id);
-//       ASSERT_TRUE(ret.HasValue());
-//       ASSERT_TRUE(*ret);
-//     }
-
-//     // Check edges
-//     ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_from->InEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     ASSERT_EQ(vertex_from->InDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     {
-//       auto ret = vertex_from->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_from->OutEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     ASSERT_EQ(vertex_from->OutDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     {
-//       auto ret = vertex_to->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et);
-//       ASSERT_EQ(e.FromVertex(), *vertex_from);
-//       ASSERT_EQ(e.ToVertex(), *vertex_to);
-//     }
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check dataset
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex_from = acc.FindVertex(gid_from, View::NEW);
-//     auto vertex_to = acc.FindVertex(gid_to, View::NEW);
-//     ASSERT_FALSE(vertex_from);
-//     ASSERT_TRUE(vertex_to);
-
-//     // Check edges
-//     ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0);
-//     ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0);
-//     ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0);
-//   }
-// }
-
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}});
-//   memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-//   memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-
-//   // Create dataset
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex1 = acc.CreateVertex();
-//     auto vertex2 = acc.CreateVertex();
-
-//     gid_vertex1 = vertex1.Gid();
-//     gid_vertex2 = vertex2.Gid();
-
-//     auto et1 = acc.NameToEdgeType("et1");
-//     auto et2 = acc.NameToEdgeType("et2");
-//     auto et3 = acc.NameToEdgeType("et3");
-//     auto et4 = acc.NameToEdgeType("et4");
-
-//     auto res1 = acc.CreateEdge(&vertex1, &vertex2, et1);
-//     ASSERT_TRUE(res1.HasValue());
-//     auto edge1 = res1.GetValue();
-//     ASSERT_EQ(edge1.EdgeType(), et1);
-//     ASSERT_EQ(edge1.FromVertex(), vertex1);
-//     ASSERT_EQ(edge1.ToVertex(), vertex2);
-
-//     auto res2 = acc.CreateEdge(&vertex2, &vertex1, et2);
-//     ASSERT_TRUE(res2.HasValue());
-//     auto edge2 = res2.GetValue();
-//     ASSERT_EQ(edge2.EdgeType(), et2);
-//     ASSERT_EQ(edge2.FromVertex(), vertex2);
-//     ASSERT_EQ(edge2.ToVertex(), vertex1);
-
-//     auto res3 = acc.CreateEdge(&vertex1, &vertex1, et3);
-//     ASSERT_TRUE(res3.HasValue());
-//     auto edge3 = res3.GetValue();
-//     ASSERT_EQ(edge3.EdgeType(), et3);
-//     ASSERT_EQ(edge3.FromVertex(), vertex1);
-//     ASSERT_EQ(edge3.ToVertex(), vertex1);
-
-//     auto res4 = acc.CreateEdge(&vertex2, &vertex2, et4);
-//     ASSERT_TRUE(res4.HasValue());
-//     auto edge4 = res4.GetValue();
-//     ASSERT_EQ(edge4.EdgeType(), et4);
-//     ASSERT_EQ(edge4.FromVertex(), vertex2);
-//     ASSERT_EQ(edge4.ToVertex(), vertex2);
-
-//     // Check edges
-//     {
-//       auto ret = vertex1.InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1.InDegree(View::NEW), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et2);
-//         ASSERT_EQ(e.FromVertex(), vertex2);
-//         ASSERT_EQ(e.ToVertex(), vertex1);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et3);
-//         ASSERT_EQ(e.FromVertex(), vertex1);
-//         ASSERT_EQ(e.ToVertex(), vertex1);
-//       }
-//     }
-//     {
-//       auto ret = vertex1.OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1.OutDegree(View::NEW), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et1);
-//         ASSERT_EQ(e.FromVertex(), vertex1);
-//         ASSERT_EQ(e.ToVertex(), vertex2);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et3);
-//         ASSERT_EQ(e.FromVertex(), vertex1);
-//         ASSERT_EQ(e.ToVertex(), vertex1);
-//       }
-//     }
-//     {
-//       auto ret = vertex2.InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2.InDegree(View::NEW), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et1);
-//         ASSERT_EQ(e.FromVertex(), vertex1);
-//         ASSERT_EQ(e.ToVertex(), vertex2);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et4);
-//         ASSERT_EQ(e.FromVertex(), vertex2);
-//         ASSERT_EQ(e.ToVertex(), vertex2);
-//       }
-//     }
-//     {
-//       auto ret = vertex2.OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2.OutDegree(View::NEW), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et2);
-//         ASSERT_EQ(e.FromVertex(), vertex2);
-//         ASSERT_EQ(e.ToVertex(), vertex1);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et4);
-//         ASSERT_EQ(e.FromVertex(), vertex2);
-//         ASSERT_EQ(e.ToVertex(), vertex2);
-//       }
-//     }
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Detach delete vertex, but abort the transaction
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex1 = acc.FindVertex(gid_vertex1, View::NEW);
-//     auto vertex2 = acc.FindVertex(gid_vertex2, View::NEW);
-//     ASSERT_TRUE(vertex1);
-//     ASSERT_TRUE(vertex2);
-
-//     auto et1 = acc.NameToEdgeType("et1");
-//     auto et2 = acc.NameToEdgeType("et2");
-//     auto et3 = acc.NameToEdgeType("et3");
-//     auto et4 = acc.NameToEdgeType("et4");
-
-//     // Delete must fail
-//     {
-//       auto ret = acc.DeleteVertex(&*vertex1);
-//       ASSERT_TRUE(ret.HasError());
-//       ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES);
-//     }
-
-//     // Detach delete vertex
-//     {
-//       auto ret = acc.DetachDeleteVertex(&*vertex1);
-//       ASSERT_TRUE(ret.HasValue());
-//       ASSERT_TRUE(*ret);
-//     }
-
-//     // Check edges
-//     {
-//       auto ret = vertex1->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->InDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et2);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et3);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//     }
-//     ASSERT_EQ(vertex1->InEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     ASSERT_EQ(vertex1->InDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     {
-//       auto ret = vertex1->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->OutDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et1);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et3);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//     }
-//     ASSERT_EQ(vertex1->OutEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     ASSERT_EQ(vertex1->OutDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     {
-//       auto ret = vertex2->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->InDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et1);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et4);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//     }
-//     {
-//       auto ret = vertex2->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex2->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et4);
-//       ASSERT_EQ(e.FromVertex(), *vertex2);
-//       ASSERT_EQ(e.ToVertex(), *vertex2);
-//     }
-//     {
-//       auto ret = vertex2->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->OutDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et2);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et4);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//     }
-//     {
-//       auto ret = vertex2->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex2->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et4);
-//       ASSERT_EQ(e.FromVertex(), *vertex2);
-//       ASSERT_EQ(e.ToVertex(), *vertex2);
-//     }
-
-//     acc.Abort();
-//   }
-
-//   // Check dataset
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex1 = acc.FindVertex(gid_vertex1, View::NEW);
-//     auto vertex2 = acc.FindVertex(gid_vertex2, View::NEW);
-//     ASSERT_TRUE(vertex1);
-//     ASSERT_TRUE(vertex2);
-
-//     auto et1 = acc.NameToEdgeType("et1");
-//     auto et2 = acc.NameToEdgeType("et2");
-//     auto et3 = acc.NameToEdgeType("et3");
-//     auto et4 = acc.NameToEdgeType("et4");
-
-//     // Check edges
-//     {
-//       auto ret = vertex1->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->InDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et2);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et3);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//     }
-//     {
-//       auto ret = vertex1->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->InDegree(View::NEW), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et2);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et3);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//     }
-//     {
-//       auto ret = vertex1->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->OutDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et1);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et3);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//     }
-//     {
-//       auto ret = vertex1->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->OutDegree(View::NEW), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et1);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et3);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//     }
-//     {
-//       auto ret = vertex2->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->InDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et1);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et4);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//     }
-//     {
-//       auto ret = vertex2->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->InDegree(View::NEW), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et1);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et4);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//     }
-//     {
-//       auto ret = vertex2->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->OutDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et2);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et4);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//     }
-//     {
-//       auto ret = vertex2->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->OutDegree(View::NEW), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et2);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et4);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//     }
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Detach delete vertex
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex1 = acc.FindVertex(gid_vertex1, View::NEW);
-//     auto vertex2 = acc.FindVertex(gid_vertex2, View::NEW);
-//     ASSERT_TRUE(vertex1);
-//     ASSERT_TRUE(vertex2);
-
-//     auto et1 = acc.NameToEdgeType("et1");
-//     auto et2 = acc.NameToEdgeType("et2");
-//     auto et3 = acc.NameToEdgeType("et3");
-//     auto et4 = acc.NameToEdgeType("et4");
-
-//     // Delete must fail
-//     {
-//       auto ret = acc.DeleteVertex(&*vertex1);
-//       ASSERT_TRUE(ret.HasError());
-//       ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES);
-//     }
-
-//     // Detach delete vertex
-//     {
-//       auto ret = acc.DetachDeleteVertex(&*vertex1);
-//       ASSERT_TRUE(ret.HasValue());
-//       ASSERT_TRUE(*ret);
-//     }
-
-//     // Check edges
-//     {
-//       auto ret = vertex1->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->InDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et2);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et3);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//     }
-//     ASSERT_EQ(vertex1->InEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     ASSERT_EQ(vertex1->InDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     {
-//       auto ret = vertex1->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->OutDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et1);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et3);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//     }
-//     ASSERT_EQ(vertex1->OutEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     ASSERT_EQ(vertex1->OutDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT);
-//     {
-//       auto ret = vertex2->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->InDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et1);
-//         ASSERT_EQ(e.FromVertex(), *vertex1);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et4);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//     }
-//     {
-//       auto ret = vertex2->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex2->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et4);
-//       ASSERT_EQ(e.FromVertex(), *vertex2);
-//       ASSERT_EQ(e.ToVertex(), *vertex2);
-//     }
-//     {
-//       auto ret = vertex2->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType();
-//       }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->OutDegree(View::OLD), 2);
-//       {
-//         auto e = edges[0];
-//         ASSERT_EQ(e.EdgeType(), et2);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex1);
-//       }
-//       {
-//         auto e = edges[1];
-//         ASSERT_EQ(e.EdgeType(), et4);
-//         ASSERT_EQ(e.FromVertex(), *vertex2);
-//         ASSERT_EQ(e.ToVertex(), *vertex2);
-//       }
-//     }
-//     {
-//       auto ret = vertex2->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex2->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et4);
-//       ASSERT_EQ(e.FromVertex(), *vertex2);
-//       ASSERT_EQ(e.ToVertex(), *vertex2);
-//     }
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check dataset
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex1 = acc.FindVertex(gid_vertex1, View::NEW);
-//     auto vertex2 = acc.FindVertex(gid_vertex2, View::NEW);
-//     ASSERT_FALSE(vertex1);
-//     ASSERT_TRUE(vertex2);
-
-//     auto et4 = acc.NameToEdgeType("et4");
-
-//     // Check edges
-//     {
-//       auto ret = vertex2->InEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex2->InDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et4);
-//       ASSERT_EQ(e.FromVertex(), *vertex2);
-//       ASSERT_EQ(e.ToVertex(), *vertex2);
-//     }
-//     {
-//       auto ret = vertex2->InEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex2->InDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et4);
-//       ASSERT_EQ(e.FromVertex(), *vertex2);
-//       ASSERT_EQ(e.ToVertex(), *vertex2);
-//     }
-//     {
-//       auto ret = vertex2->OutEdges(View::OLD);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex2->OutDegree(View::OLD), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et4);
-//       ASSERT_EQ(e.FromVertex(), *vertex2);
-//       ASSERT_EQ(e.ToVertex(), *vertex2);
-//     }
-//     {
-//       auto ret = vertex2->OutEdges(View::NEW);
-//       ASSERT_TRUE(ret.HasValue());
-//       auto edges = ret.GetValue();
-//       ASSERT_EQ(edges.size(), 1);
-//       ASSERT_EQ(*vertex2->OutDegree(View::NEW), 1);
-//       auto e = edges[0];
-//       ASSERT_EQ(e.EdgeType(), et4);
-//       ASSERT_EQ(e.FromVertex(), *vertex2);
-//       ASSERT_EQ(e.ToVertex(), *vertex2);
-//     }
-//   }
-// }
-
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST(StorageWithProperties, EdgePropertyCommit) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = true}});
-//   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.CreateVertex();
-//     gid = vertex.Gid();
-//     auto et = acc.NameToEdgeType("et5");
-//     auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), vertex);
-//     ASSERT_EQ(edge.ToVertex(), vertex);
-
-//     auto property = acc.NameToProperty("property5");
-
-//     ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     {
-//       auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("temporary"));
-//       ASSERT_TRUE(old_value.HasValue());
-//       ASSERT_TRUE(old_value->IsNull());
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "temporary");
-//     {
-//       auto properties = edge.Properties(View::NEW).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "temporary");
-//     }
-
-//     {
-//       auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("nandare"));
-//       ASSERT_TRUE(old_value.HasValue());
-//       ASSERT_FALSE(old_value->IsNull());
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::NEW).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     acc.Commit(GetNextHlc());
-//   }
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property = acc.NameToProperty("property5");
-
-//     ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::OLD).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::NEW).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     auto other_property = acc.NameToProperty("other");
-
-//     ASSERT_TRUE(edge.GetProperty(other_property, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(other_property, View::NEW)->IsNull());
-
-//     acc.Abort();
-//   }
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property = acc.NameToProperty("property5");
-
-//     {
-//       auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue());
-//       ASSERT_TRUE(old_value.HasValue());
-//       ASSERT_FALSE(old_value->IsNull());
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::OLD).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     {
-//       auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue());
-//       ASSERT_TRUE(old_value.HasValue());
-//       ASSERT_TRUE(old_value->IsNull());
-//     }
-
-//     acc.Commit(GetNextHlc());
-//   }
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property = acc.NameToProperty("property5");
-
-//     ASSERT_TRUE(edge.GetProperty(property, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::OLD)->size(), 0);
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     auto other_property = acc.NameToProperty("other");
-
-//     ASSERT_TRUE(edge.GetProperty(other_property, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(other_property, View::NEW)->IsNull());
-
-//     acc.Abort();
-//   }
-// }
-
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST(StorageWithProperties, EdgePropertyAbort) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = true}});
-//   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-
-//   // Create the vertex.
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.CreateVertex();
-//     gid = vertex.Gid();
-//     auto et = acc.NameToEdgeType("et5");
-//     auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), vertex);
-//     ASSERT_EQ(edge.ToVertex(), vertex);
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Set property 5 to "nandare", but abort the transaction.
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property = acc.NameToProperty("property5");
-
-//     ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     {
-//       auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("temporary"));
-//       ASSERT_TRUE(old_value.HasValue());
-//       ASSERT_TRUE(old_value->IsNull());
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "temporary");
-//     {
-//       auto properties = edge.Properties(View::NEW).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "temporary");
-//     }
-
-//     {
-//       auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("nandare"));
-//       ASSERT_TRUE(old_value.HasValue());
-//       ASSERT_FALSE(old_value->IsNull());
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::NEW).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     acc.Abort();
-//   }
-
-//   // Check that property 5 is null.
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property = acc.NameToProperty("property5");
-
-//     ASSERT_TRUE(edge.GetProperty(property, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::OLD)->size(), 0);
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     auto other_property = acc.NameToProperty("other");
-
-//     ASSERT_TRUE(edge.GetProperty(other_property, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(other_property, View::NEW)->IsNull());
-
-//     acc.Abort();
-//   }
-
-//   // Set property 5 to "nandare".
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property = acc.NameToProperty("property5");
-
-//     ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     {
-//       auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("temporary"));
-//       ASSERT_TRUE(old_value.HasValue());
-//       ASSERT_TRUE(old_value->IsNull());
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "temporary");
-//     {
-//       auto properties = edge.Properties(View::NEW).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "temporary");
-//     }
-
-//     {
-//       auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("nandare"));
-//       ASSERT_TRUE(old_value.HasValue());
-//       ASSERT_FALSE(old_value->IsNull());
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::NEW).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check that property 5 is "nandare".
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property = acc.NameToProperty("property5");
-
-//     ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::OLD).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::NEW).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     auto other_property = acc.NameToProperty("other");
-
-//     ASSERT_TRUE(edge.GetProperty(other_property, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(other_property, View::NEW)->IsNull());
-
-//     acc.Abort();
-//   }
-
-//   // Set property 5 to null, but abort the transaction.
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property = acc.NameToProperty("property5");
-
-//     ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::OLD).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::NEW).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     {
-//       auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue());
-//       ASSERT_TRUE(old_value.HasValue());
-//       ASSERT_FALSE(old_value->IsNull());
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::OLD).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     acc.Abort();
-//   }
-
-//   // Check that property 5 is "nandare".
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property = acc.NameToProperty("property5");
-
-//     ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::OLD).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::NEW).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     auto other_property = acc.NameToProperty("other");
-
-//     ASSERT_TRUE(edge.GetProperty(other_property, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(other_property, View::NEW)->IsNull());
-
-//     acc.Abort();
-//   }
-
-//   // Set property 5 to null.
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property = acc.NameToProperty("property5");
-
-//     ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::OLD).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::NEW).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     {
-//       auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue());
-//       ASSERT_TRUE(old_value.HasValue());
-//       ASSERT_FALSE(old_value->IsNull());
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare");
-//     {
-//       auto properties = edge.Properties(View::OLD).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property].ValueString(), "nandare");
-//     }
-
-//     ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   // Check that property 5 is null.
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property = acc.NameToProperty("property5");
-
-//     ASSERT_TRUE(edge.GetProperty(property, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::OLD)->size(), 0);
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     auto other_property = acc.NameToProperty("other");
-
-//     ASSERT_TRUE(edge.GetProperty(other_property, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(other_property, View::NEW)->IsNull());
-
-//     acc.Abort();
-//   }
-// }
-
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST(StorageWithProperties, EdgePropertySerializationError) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = true}});
-//   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.CreateVertex();
-//     gid = vertex.Gid();
-//     auto et = acc.NameToEdgeType("et5");
-//     auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), vertex);
-//     ASSERT_EQ(edge.ToVertex(), vertex);
-//     acc.Commit(GetNextHlc());
-//   }
-
-//   auto acc1 = store.Access(GetNextHlc());
-//   auto acc2 = store.Access(GetNextHlc());
-
-//   // Set property 1 to 123 in accessor 1.
-//   {
-//     auto vertex = acc1.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property1 = acc1.NameToProperty("property1");
-//     auto property2 = acc1.NameToProperty("property2");
-
-//     ASSERT_TRUE(edge.GetProperty(property1, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property1, View::NEW)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property2, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::OLD)->size(), 0);
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     {
-//       auto old_value = edge.SetProperty(property1, memgraph::storage::PropertyValue(123));
-//       ASSERT_TRUE(old_value.HasValue());
-//       ASSERT_TRUE(old_value->IsNull());
-//     }
-
-//     ASSERT_TRUE(edge.GetProperty(property1, View::OLD)->IsNull());
-//     ASSERT_EQ(edge.GetProperty(property1, View::NEW)->ValueInt(), 123);
-//     ASSERT_TRUE(edge.GetProperty(property2, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::OLD)->size(), 0);
-//     {
-//       auto properties = edge.Properties(View::NEW).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property1].ValueInt(), 123);
-//     }
-//   }
-
-//   // Set property 2 to "nandare" in accessor 2.
-//   {
-//     auto vertex = acc2.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property1 = acc2.NameToProperty("property1");
-//     auto property2 = acc2.NameToProperty("property2");
-
-//     ASSERT_TRUE(edge.GetProperty(property1, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property1, View::NEW)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property2, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::OLD)->size(), 0);
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     {
-//       auto res = edge.SetProperty(property2, memgraph::storage::PropertyValue("nandare"));
-//       ASSERT_TRUE(res.HasError());
-//       ASSERT_EQ(res.GetError(), memgraph::storage::Error::SERIALIZATION_ERROR);
-//     }
-//   }
-
-//   // Finalize both accessors.
-//   ASSERT_FALSE(acc1.Commit(GetNextHlc());
-//   acc2.Abort();
-
-//   // Check which properties exist.
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property1 = acc.NameToProperty("property1");
-//     auto property2 = acc.NameToProperty("property2");
-
-//     ASSERT_EQ(edge.GetProperty(property1, View::OLD)->ValueInt(), 123);
-//     ASSERT_TRUE(edge.GetProperty(property2, View::OLD)->IsNull());
-//     {
-//       auto properties = edge.Properties(View::OLD).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property1].ValueInt(), 123);
-//     }
-
-//     ASSERT_EQ(edge.GetProperty(property1, View::NEW)->ValueInt(), 123);
-//     ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull());
-//     {
-//       auto properties = edge.Properties(View::NEW).GetValue();
-//       ASSERT_EQ(properties.size(), 1);
-//       ASSERT_EQ(properties[property1].ValueInt(), 123);
-//     }
-
-//     acc.Abort();
-//   }
-// }
-
-// TEST(StorageWithProperties, EdgePropertyClear) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = true}});
-//   memgraph::storage::Gid gid;
-//   auto property1 = store.NameToProperty("property1");
-//   auto property2 = store.NameToProperty("property2");
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.CreateVertex();
-//     gid = vertex.Gid();
-//     auto et = acc.NameToEdgeType("et5");
-//     auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), vertex);
-//     ASSERT_EQ(edge.ToVertex(), vertex);
-
-//     auto old_value = edge.SetProperty(property1, memgraph::storage::PropertyValue("value"));
-//     ASSERT_TRUE(old_value.HasValue());
-//     ASSERT_TRUE(old_value->IsNull());
-
-//     acc.Commit(GetNextHlc());
-//   }
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     ASSERT_EQ(edge.GetProperty(property1, View::OLD)->ValueString(), "value");
-//     ASSERT_TRUE(edge.GetProperty(property2, View::OLD)->IsNull());
-//     ASSERT_THAT(edge.Properties(View::OLD).GetValue(),
-//                 UnorderedElementsAre(std::pair(property1, memgraph::storage::PropertyValue("value"))));
-
-//     {
-//       auto old_values = edge.ClearProperties();
-//       ASSERT_TRUE(old_values.HasValue());
-//       ASSERT_FALSE(old_values->empty());
-//     }
-
-//     ASSERT_TRUE(edge.GetProperty(property1, View::NEW)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW).GetValue().size(), 0);
-
-//     {
-//       auto old_values = edge.ClearProperties();
-//       ASSERT_TRUE(old_values.HasValue());
-//       ASSERT_TRUE(old_values->empty());
-//     }
-
-//     ASSERT_TRUE(edge.GetProperty(property1, View::NEW)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW).GetValue().size(), 0);
-
-//     acc.Abort();
-//   }
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto old_value = edge.SetProperty(property2, memgraph::storage::PropertyValue(42));
-//     ASSERT_TRUE(old_value.HasValue());
-//     ASSERT_TRUE(old_value->IsNull());
-
-//     acc.Commit(GetNextHlc());
-//   }
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     ASSERT_EQ(edge.GetProperty(property1, View::OLD)->ValueString(), "value");
-//     ASSERT_EQ(edge.GetProperty(property2, View::OLD)->ValueInt(), 42);
-//     ASSERT_THAT(edge.Properties(View::OLD).GetValue(),
-//                 UnorderedElementsAre(std::pair(property1, memgraph::storage::PropertyValue("value")),
-//                                      std::pair(property2, memgraph::storage::PropertyValue(42))));
-
-//     {
-//       auto old_values = edge.ClearProperties();
-//       ASSERT_TRUE(old_values.HasValue());
-//       ASSERT_FALSE(old_values->empty());
-//     }
-
-//     ASSERT_TRUE(edge.GetProperty(property1, View::NEW)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW).GetValue().size(), 0);
-
-//     {
-//       auto old_values = edge.ClearProperties();
-//       ASSERT_TRUE(old_values.HasValue());
-//       ASSERT_TRUE(old_values->empty());
-//     }
-
-//     ASSERT_TRUE(edge.GetProperty(property1, View::NEW)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW).GetValue().size(), 0);
-
-//     acc.Commit(GetNextHlc());
-//   }
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     ASSERT_TRUE(edge.GetProperty(property1, View::NEW)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW).GetValue().size(), 0);
-
-//     acc.Abort();
-//   }
-// }
-
-// // NOLINTNEXTLINE(hicpp-special-member-functions)
-// TEST(StorageWithoutProperties, EdgePropertyAbort) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = false}});
-//   memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max());
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.CreateVertex();
-//     gid = vertex.Gid();
-//     auto et = acc.NameToEdgeType("et5");
-//     auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), vertex);
-//     ASSERT_EQ(edge.ToVertex(), vertex);
-//     acc.Commit(GetNextHlc());
-//   }
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property = acc.NameToProperty("property5");
-
-//     ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     {
-//       auto res = edge.SetProperty(property, memgraph::storage::PropertyValue("temporary"));
-//       ASSERT_TRUE(res.HasError());
-//       ASSERT_EQ(res.GetError(), memgraph::storage::Error::PROPERTIES_DISABLED);
-//     }
-
-//     ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     {
-//       auto res = edge.SetProperty(property, memgraph::storage::PropertyValue("nandare"));
-//       ASSERT_TRUE(res.HasError());
-//       ASSERT_EQ(res.GetError(), memgraph::storage::Error::PROPERTIES_DISABLED);
-//     }
-
-//     ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     acc.Abort();
-//   }
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     auto property = acc.NameToProperty("property5");
-
-//     ASSERT_TRUE(edge.GetProperty(property, View::OLD)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::OLD)->size(), 0);
-
-//     ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull());
-//     ASSERT_EQ(edge.Properties(View::NEW)->size(), 0);
-
-//     auto other_property = acc.NameToProperty("other");
-
-//     ASSERT_TRUE(edge.GetProperty(other_property, View::OLD)->IsNull());
-//     ASSERT_TRUE(edge.GetProperty(other_property, View::NEW)->IsNull());
-
-//     acc.Abort();
-//   }
-// }
-
-// TEST(StorageWithoutProperties, EdgePropertyClear) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = false}});
-//   memgraph::storage::Gid gid;
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.CreateVertex();
-//     gid = vertex.Gid();
-//     auto et = acc.NameToEdgeType("et5");
-//     auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue();
-//     ASSERT_EQ(edge.EdgeType(), et);
-//     ASSERT_EQ(edge.FromVertex(), vertex);
-//     ASSERT_EQ(edge.ToVertex(), vertex);
-//     acc.Commit(GetNextHlc());
-//   }
-//   {
-//     auto acc = store.Access(GetNextHlc());
-//     auto vertex = acc.FindVertex(gid, View::OLD);
-//     ASSERT_TRUE(vertex);
-//     auto edge = vertex->OutEdges(View::NEW).GetValue()[0];
-
-//     ASSERT_EQ(edge.ClearProperties().GetError(), memgraph::storage::Error::PROPERTIES_DISABLED);
-
-//     acc.Abort();
-//   }
-// }
-
-// TEST(StorageWithProperties, EdgeNonexistentPropertyAPI) {
-//   memgraph::storage::Storage store({.items = {.properties_on_edges = true}});
-
-//   auto property = store.NameToProperty("property");
-
-//   auto acc = store.Access(GetNextHlc());
-//   auto vertex = acc.CreateVertex();
-//   auto edge = acc.CreateEdge(&vertex, &vertex, acc.NameToEdgeType("edge"));
-//   ASSERT_TRUE(edge.HasValue());
-
-//   // Check state before (OLD view).
-//   ASSERT_EQ(edge->Properties(View::OLD).GetError(), memgraph::storage::Error::NONEXISTENT_OBJECT);
-//   ASSERT_EQ(edge->GetProperty(property, View::OLD).GetError(), memgraph::storage::Error::NONEXISTENT_OBJECT);
-
-//   // Check state before (NEW view).
-//   ASSERT_EQ(edge->Properties(View::NEW)->size(), 0);
-//   ASSERT_EQ(*edge->GetProperty(property, View::NEW), memgraph::storage::PropertyValue());
-
-//   // Modify edge.
-//   ASSERT_TRUE(edge->SetProperty(property, memgraph::storage::PropertyValue("value"))->IsNull());
-
-//   // Check state after (OLD view).
-//   ASSERT_EQ(edge->Properties(View::OLD).GetError(), memgraph::storage::Error::NONEXISTENT_OBJECT);
-//   ASSERT_EQ(edge->GetProperty(property, View::OLD).GetError(), memgraph::storage::Error::NONEXISTENT_OBJECT);
-
-//   // Check state after (NEW view).
-//   ASSERT_EQ(edge->Properties(View::NEW)->size(), 1);
-//   ASSERT_EQ(*edge->GetProperty(property, View::NEW), memgraph::storage::PropertyValue("value"));
-
-//   acc.Commit(GetNextHlc());
-// }
 }  // namespace memgraph::storage::v3::tests