From e1399582b85f86e8f5645d1325ef989bb2d2c458 Mon Sep 17 00:00:00 2001 From: Teon Banek <teon.banek@memgraph.io> Date: Tue, 26 Nov 2019 12:46:29 +0100 Subject: [PATCH] Implement SatisfiesType on CypherType classes Reviewers: mferencevic, ipaljak Reviewed By: mferencevic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2571 --- src/query/procedure/cypher_types.hpp | 127 +++++++++- tests/unit/query_procedure_mgp_type.cpp | 312 ++++++++++++++++++++++++ 2 files changed, 432 insertions(+), 7 deletions(-) diff --git a/src/query/procedure/cypher_types.hpp b/src/query/procedure/cypher_types.hpp index c5dd099b3..d614cf43b 100644 --- a/src/query/procedure/cypher_types.hpp +++ b/src/query/procedure/cypher_types.hpp @@ -1,10 +1,13 @@ /// @file #pragma once +#include "mg_procedure.h" + #include <functional> #include <memory> #include <string_view> +#include "query/typed_value.hpp" #include "utils/memory.hpp" #include "utils/pmr/string.hpp" @@ -27,8 +30,11 @@ class CypherType { /// Get name of the type as it should be presented to the user. virtual std::string_view GetPresentableName() const = 0; - // TODO: Type checking - // virtual bool SatisfiesType(const mgp_value &) const = 0; + /// Return true if given mgp_value is of the type as described by `this`. + virtual bool SatisfiesType(const mgp_value &) const = 0; + + /// Return true if given TypedValue is of the type as described by `this`. + virtual bool SatisfiesType(const query::TypedValue &) const = 0; // The following methods are a simple replacement for RTTI because we have // some special cases we need to handle. @@ -44,36 +50,92 @@ using CypherTypePtr = class AnyType : public CypherType { public: std::string_view GetPresentableName() const override { return "ANY"; } + + bool SatisfiesType(const mgp_value &value) const override { + return !mgp_value_is_null(&value); + } + + bool SatisfiesType(const query::TypedValue &value) const override { + return !value.IsNull(); + } }; class BoolType : public CypherType { public: std::string_view GetPresentableName() const override { return "BOOLEAN"; } + + bool SatisfiesType(const mgp_value &value) const override { + return mgp_value_is_bool(&value); + } + + bool SatisfiesType(const query::TypedValue &value) const override { + return value.IsBool(); + } }; class StringType : public CypherType { public: std::string_view GetPresentableName() const override { return "STRING"; } + + bool SatisfiesType(const mgp_value &value) const override { + return mgp_value_is_string(&value); + } + + bool SatisfiesType(const query::TypedValue &value) const override { + return value.IsString(); + } }; class IntType : public CypherType { public: std::string_view GetPresentableName() const override { return "INTEGER"; } + + bool SatisfiesType(const mgp_value &value) const override { + return mgp_value_is_int(&value); + } + + bool SatisfiesType(const query::TypedValue &value) const override { + return value.IsInt(); + } }; class FloatType : public CypherType { public: std::string_view GetPresentableName() const override { return "FLOAT"; } + + bool SatisfiesType(const mgp_value &value) const override { + return mgp_value_is_double(&value); + } + + bool SatisfiesType(const query::TypedValue &value) const override { + return value.IsDouble(); + } }; class NumberType : public CypherType { public: std::string_view GetPresentableName() const override { return "NUMBER"; } + + bool SatisfiesType(const mgp_value &value) const override { + return mgp_value_is_int(&value) || mgp_value_is_double(&value); + } + + bool SatisfiesType(const query::TypedValue &value) const override { + return value.IsInt() || value.IsDouble(); + } }; class NodeType : public CypherType { public: std::string_view GetPresentableName() const override { return "NODE"; } + + bool SatisfiesType(const mgp_value &value) const override { + return mgp_value_is_vertex(&value); + } + + bool SatisfiesType(const query::TypedValue &value) const override { + return value.IsVertex(); + } }; class RelationshipType : public CypherType { @@ -81,11 +143,27 @@ class RelationshipType : public CypherType { std::string_view GetPresentableName() const override { return "RELATIONSHIP"; } + + bool SatisfiesType(const mgp_value &value) const override { + return mgp_value_is_edge(&value); + } + + bool SatisfiesType(const query::TypedValue &value) const override { + return value.IsEdge(); + } }; class PathType : public CypherType { public: std::string_view GetPresentableName() const override { return "PATH"; } + + bool SatisfiesType(const mgp_value &value) const override { + return mgp_value_is_path(&value); + } + + bool SatisfiesType(const query::TypedValue &value) const override { + return value.IsPath(); + } }; // TODO: There's also Temporal Types, but we currently do not support those. @@ -98,26 +176,53 @@ class PathType : public CypherType { class MapType : public CypherType { public: std::string_view GetPresentableName() const override { return "MAP"; } + + bool SatisfiesType(const mgp_value &value) const override { + return mgp_value_is_map(&value) || mgp_value_is_vertex(&value) || + mgp_value_is_edge(&value); + } + + bool SatisfiesType(const query::TypedValue &value) const override { + return value.IsMap() || value.IsVertex() || value.IsEdge(); + } }; // Composite Types class ListType : public CypherType { public: - CypherTypePtr type_; + CypherTypePtr element_type_; utils::pmr::string presentable_name_; /// @throw std::bad_alloc /// @throw std::length_error - explicit ListType(CypherTypePtr type, utils::MemoryResource *memory) - : type_(std::move(type)), presentable_name_("LIST OF ", memory) { - presentable_name_.append(type_->GetPresentableName()); + explicit ListType(CypherTypePtr element_type, utils::MemoryResource *memory) + : element_type_(std::move(element_type)), + presentable_name_("LIST OF ", memory) { + presentable_name_.append(element_type_->GetPresentableName()); } std::string_view GetPresentableName() const override { return presentable_name_; } + bool SatisfiesType(const mgp_value &value) const override { + if (!mgp_value_is_list(&value)) return false; + const auto *list = mgp_value_get_list(&value); + for (size_t i = 0; i < mgp_list_size(list); ++i) { + if (!element_type_->SatisfiesType(*mgp_list_at(list, i))) return false; + } + return true; + } + + bool SatisfiesType(const query::TypedValue &value) const override { + if (!value.IsList()) return false; + for (const auto &elem : value.ValueList()) { + if (!element_type_->SatisfiesType(elem)) return false; + } + return true; + } + const ListType *AsListType() const override { return this; } }; @@ -135,7 +240,7 @@ class NullableType : public CypherType { // ListType is specially formatted if (list_type) { presentable_name_.assign("LIST? OF ") - .append(list_type->type_->GetPresentableName()); + .append(list_type->element_type_->GetPresentableName()); } else { presentable_name_.assign(type_->GetPresentableName()).append("?"); } @@ -167,6 +272,14 @@ class NullableType : public CypherType { return presentable_name_; } + bool SatisfiesType(const mgp_value &value) const override { + return mgp_value_is_null(&value) || type_->SatisfiesType(value); + } + + bool SatisfiesType(const query::TypedValue &value) const override { + return value.IsNull() || type_->SatisfiesType(value); + } + const NullableType *AsNullableType() const override { return this; } }; diff --git a/tests/unit/query_procedure_mgp_type.cpp b/tests/unit/query_procedure_mgp_type.cpp index 86ef0c174..1894bf199 100644 --- a/tests/unit/query_procedure_mgp_type.cpp +++ b/tests/unit/query_procedure_mgp_type.cpp @@ -56,3 +56,315 @@ TEST(CypherType, PresentableNameCompositeTypes) { "LIST? OF LIST? OF STRING?"); } } + +TEST(CypherType, NullSatisfiesType) { + mgp_memory memory{utils::NewDeleteResource()}; + { + auto *mgp_null = mgp_value_make_null(&memory); + const query::TypedValue tv_null; + std::vector<const mgp_type *> primitive_types{ + mgp_type_any(), mgp_type_bool(), mgp_type_string(), + mgp_type_int(), mgp_type_float(), mgp_type_number(), + mgp_type_map(), mgp_type_node(), mgp_type_relationship(), + mgp_type_path()}; + for (const auto *primitive_type : primitive_types) { + for (const auto *type : + {primitive_type, mgp_type_list(primitive_type), + mgp_type_list(mgp_type_nullable(primitive_type))}) { + EXPECT_FALSE(type->impl->SatisfiesType(*mgp_null)); + EXPECT_FALSE(type->impl->SatisfiesType(tv_null)); + const auto *null_type = mgp_type_nullable(type); + EXPECT_TRUE(null_type->impl->SatisfiesType(*mgp_null)); + EXPECT_TRUE(null_type->impl->SatisfiesType(tv_null)); + } + } + } +} + +static void CheckSatisfiesTypesAndNullable( + const mgp_value *mgp_val, const query::TypedValue &tv, + const std::vector<const mgp_type *> &types) { + for (const auto *type : types) { + EXPECT_TRUE(type->impl->SatisfiesType(*mgp_val)) + << type->impl->GetPresentableName(); + EXPECT_TRUE(type->impl->SatisfiesType(tv)); + const auto *null_type = mgp_type_nullable(type); + EXPECT_TRUE(null_type->impl->SatisfiesType(*mgp_val)) + << null_type->impl->GetPresentableName(); + EXPECT_TRUE(null_type->impl->SatisfiesType(tv)); + } +} + +static void CheckNotSatisfiesTypesAndListAndNullable( + const mgp_value *mgp_val, const query::TypedValue &tv, + const std::vector<const mgp_type *> &elem_types) { + for (const auto *elem_type : elem_types) { + for (const auto *type : {elem_type, mgp_type_list(elem_type), + mgp_type_list(mgp_type_nullable(elem_type))}) { + EXPECT_FALSE(type->impl->SatisfiesType(*mgp_val)) + << type->impl->GetPresentableName(); + EXPECT_FALSE(type->impl->SatisfiesType(tv)); + const auto *null_type = mgp_type_nullable(type); + EXPECT_FALSE(null_type->impl->SatisfiesType(*mgp_val)) + << null_type->impl->GetPresentableName(); + EXPECT_FALSE(null_type->impl->SatisfiesType(tv)); + } + } +} + +TEST(CypherType, BoolSatisfiesType) { + mgp_memory memory{utils::NewDeleteResource()}; + auto *mgp_bool = mgp_value_make_bool(1, &memory); + const query::TypedValue tv_bool(true); + CheckSatisfiesTypesAndNullable(mgp_bool, tv_bool, + {mgp_type_any(), mgp_type_bool()}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_bool, tv_bool, + {mgp_type_string(), mgp_type_int(), mgp_type_float(), mgp_type_number(), + mgp_type_map(), mgp_type_node(), mgp_type_relationship(), + mgp_type_path()}); +} + +TEST(CypherType, IntSatisfiesType) { + mgp_memory memory{utils::NewDeleteResource()}; + auto *mgp_int = mgp_value_make_int(42, &memory); + const query::TypedValue tv_int(42); + CheckSatisfiesTypesAndNullable( + mgp_int, tv_int, {mgp_type_any(), mgp_type_int(), mgp_type_number()}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_int, tv_int, + {mgp_type_bool(), mgp_type_string(), mgp_type_float(), mgp_type_map(), + mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); +} + +TEST(CypherType, DoubleSatisfiesType) { + mgp_memory memory{utils::NewDeleteResource()}; + auto *mgp_double = mgp_value_make_double(42, &memory); + const query::TypedValue tv_double(42.0); + CheckSatisfiesTypesAndNullable( + mgp_double, tv_double, + {mgp_type_any(), mgp_type_float(), mgp_type_number()}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_double, tv_double, + {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_map(), + mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); +} + +TEST(CypherType, StringSatisfiesType) { + mgp_memory memory{utils::NewDeleteResource()}; + auto *mgp_string = mgp_value_make_string("text", &memory); + const query::TypedValue tv_string("text"); + CheckSatisfiesTypesAndNullable(mgp_string, tv_string, + {mgp_type_any(), mgp_type_string()}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_string, tv_string, + {mgp_type_bool(), mgp_type_int(), mgp_type_float(), mgp_type_number(), + mgp_type_map(), mgp_type_node(), mgp_type_relationship(), + mgp_type_path()}); +} + +TEST(CypherType, MapSatisfiesType) { + mgp_memory memory{utils::NewDeleteResource()}; + auto *map = mgp_map_make_empty(&memory); + mgp_map_insert(map, "key", mgp_value_make_int(42, &memory)); + auto *mgp_map_v = mgp_value_make_map(map); + const query::TypedValue tv_map( + std::map<std::string, query::TypedValue>{{"key", query::TypedValue(42)}}); + CheckSatisfiesTypesAndNullable(mgp_map_v, tv_map, + {mgp_type_any(), mgp_type_map()}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_map_v, tv_map, + {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), + mgp_type_number(), mgp_type_node(), mgp_type_relationship(), + mgp_type_path()}); +} + +TEST(CypherType, VertexSatisfiesType) { +#ifdef MG_SINGLE_NODE_V2 + storage::Storage db; + auto storage_dba = db.Access(); + query::DbAccessor dba(&storage_dba); +#else + database::GraphDb db; + auto storage_dba = db.Access(); + query::DbAccessor dba(&storage_dba); +#endif + auto vertex = dba.InsertVertex(); + mgp_memory memory{utils::NewDeleteResource()}; + utils::Allocator<mgp_vertex> alloc(memory.impl); + mgp_graph graph{&dba, storage::View::NEW}; + auto *mgp_vertex_v = + mgp_value_make_vertex(alloc.new_object<mgp_vertex>(vertex, &graph)); + const query::TypedValue tv_vertex(vertex); + CheckSatisfiesTypesAndNullable( + mgp_vertex_v, tv_vertex, + {mgp_type_any(), mgp_type_node(), mgp_type_map()}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_vertex_v, tv_vertex, + {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), + mgp_type_number(), mgp_type_relationship(), mgp_type_path()}); +} + +TEST(CypherType, EdgeSatisfiesType) { +#ifdef MG_SINGLE_NODE_V2 + storage::Storage db; + auto storage_dba = db.Access(); + query::DbAccessor dba(&storage_dba); +#else + database::GraphDb db; + auto storage_dba = db.Access(); + query::DbAccessor dba(&storage_dba); +#endif + auto v1 = dba.InsertVertex(); + auto v2 = dba.InsertVertex(); + auto edge = *dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("edge_type")); + mgp_memory memory{utils::NewDeleteResource()}; + utils::Allocator<mgp_edge> alloc(memory.impl); + mgp_graph graph{&dba, storage::View::NEW}; + auto *mgp_edge_v = + mgp_value_make_edge(alloc.new_object<mgp_edge>(edge, &graph)); + const query::TypedValue tv_edge(edge); + CheckSatisfiesTypesAndNullable( + mgp_edge_v, tv_edge, + {mgp_type_any(), mgp_type_relationship(), mgp_type_map()}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_edge_v, tv_edge, + {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), + mgp_type_number(), mgp_type_node(), mgp_type_path()}); +} + +TEST(CypherType, PathSatisfiesType) { +#ifdef MG_SINGLE_NODE_V2 + storage::Storage db; + auto storage_dba = db.Access(); + query::DbAccessor dba(&storage_dba); +#else + database::GraphDb db; + auto storage_dba = db.Access(); + query::DbAccessor dba(&storage_dba); +#endif + auto v1 = dba.InsertVertex(); + auto v2 = dba.InsertVertex(); + auto edge = *dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("edge_type")); + mgp_memory memory{utils::NewDeleteResource()}; + utils::Allocator<mgp_path> alloc(memory.impl); + mgp_graph graph{&dba, storage::View::NEW}; + auto *path = mgp_path_make_with_start( + alloc.new_object<mgp_vertex>(v1, &graph), &memory); + ASSERT_TRUE(path); + ASSERT_TRUE(mgp_path_expand(path, alloc.new_object<mgp_edge>(edge, &graph))); + auto *mgp_path_v = mgp_value_make_path(path); + const query::TypedValue tv_path(query::Path(v1, edge, v2)); + CheckSatisfiesTypesAndNullable(mgp_path_v, tv_path, + {mgp_type_any(), mgp_type_path()}); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_path_v, tv_path, + {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), + mgp_type_number(), mgp_type_map(), mgp_type_node(), + mgp_type_relationship()}); +} + +static std::vector<const mgp_type *> MakeListTypes( + const std::vector<const mgp_type *> &element_types) { + std::vector<const mgp_type *> list_types; + list_types.reserve(2U * element_types.size()); + for (const auto *type : element_types) { + list_types.push_back(mgp_type_list(type)); + list_types.push_back(mgp_type_list(mgp_type_nullable(type))); + } + return list_types; +} + +TEST(CypherType, EmptyListSatisfiesType) { + mgp_memory memory{utils::NewDeleteResource()}; + auto *list = mgp_list_make_empty(0, &memory); + auto *mgp_list_v = mgp_value_make_list(list); + query::TypedValue tv_list(std::vector<query::TypedValue>{}); + // Empty List satisfies all list element types + std::vector<const mgp_type *> primitive_types{ + mgp_type_any(), mgp_type_bool(), mgp_type_string(), + mgp_type_int(), mgp_type_float(), mgp_type_number(), + mgp_type_map(), mgp_type_node(), mgp_type_relationship(), + mgp_type_path()}; + auto all_types = MakeListTypes(primitive_types); + all_types.push_back(mgp_type_any()); + CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, all_types); +} + +TEST(CypherType, ListOfIntSatisfiesType) { + mgp_memory memory{utils::NewDeleteResource()}; + constexpr int64_t elem_count = 3; + auto *list = mgp_list_make_empty(elem_count, &memory); + auto *mgp_list_v = mgp_value_make_list(list); + query::TypedValue tv_list(std::vector<query::TypedValue>{}); + for (int64_t i = 0; i < elem_count; ++i) { + ASSERT_TRUE(mgp_list_append(list, mgp_value_make_int(i, &memory))); + tv_list.ValueList().emplace_back(i); + auto valid_types = + MakeListTypes({mgp_type_any(), mgp_type_int(), mgp_type_number()}); + valid_types.push_back(mgp_type_any()); + CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, valid_types); + CheckNotSatisfiesTypesAndListAndNullable( + mgp_list_v, tv_list, + {mgp_type_bool(), mgp_type_string(), mgp_type_float(), mgp_type_map(), + mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); + } +} + +TEST(CypherType, ListOfIntAndBoolSatisfiesType) { + mgp_memory memory{utils::NewDeleteResource()}; + constexpr int64_t elem_count = 2; + auto *list = mgp_list_make_empty(elem_count, &memory); + auto *mgp_list_v = mgp_value_make_list(list); + query::TypedValue tv_list(std::vector<query::TypedValue>{}); + // Add an int + ASSERT_TRUE(mgp_list_append(list, mgp_value_make_int(42, &memory))); + tv_list.ValueList().emplace_back(42); + // Add a boolean + ASSERT_TRUE(mgp_list_append(list, mgp_value_make_bool(1, &memory))); + tv_list.ValueList().emplace_back(true); + auto valid_types = MakeListTypes({mgp_type_any()}); + valid_types.push_back(mgp_type_any()); + CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, valid_types); + // All other types will not be satisfied + CheckNotSatisfiesTypesAndListAndNullable( + mgp_list_v, tv_list, + {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), + mgp_type_number(), mgp_type_map(), mgp_type_node(), + mgp_type_relationship(), mgp_type_path()}); +} + +TEST(CypherType, ListOfNullSatisfiesType) { + mgp_memory memory{utils::NewDeleteResource()}; + auto *list = mgp_list_make_empty(1, &memory); + auto *mgp_list_v = mgp_value_make_list(list); + query::TypedValue tv_list(std::vector<query::TypedValue>{}); + ASSERT_TRUE(mgp_list_append(list, mgp_value_make_null(&memory))); + tv_list.ValueList().emplace_back(); + // List with Null satisfies all nullable list element types + std::vector<const mgp_type *> primitive_types{ + mgp_type_any(), mgp_type_bool(), mgp_type_string(), + mgp_type_int(), mgp_type_float(), mgp_type_number(), + mgp_type_map(), mgp_type_node(), mgp_type_relationship(), + mgp_type_path()}; + std::vector<const mgp_type *> valid_types{mgp_type_any()}; + valid_types.reserve(1U + primitive_types.size()); + for (const auto *elem_type : primitive_types) { + valid_types.push_back(mgp_type_list(mgp_type_nullable(elem_type))); + } + CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, valid_types); + std::vector<const mgp_type *> invalid_types; + invalid_types.reserve(primitive_types.size()); + for (const auto *elem_type : primitive_types) { + invalid_types.push_back(mgp_type_list(elem_type)); + } + for (const auto *type : invalid_types) { + EXPECT_FALSE(type->impl->SatisfiesType(*mgp_list_v)) + << type->impl->GetPresentableName(); + EXPECT_FALSE(type->impl->SatisfiesType(tv_list)); + const auto *null_type = mgp_type_nullable(type); + EXPECT_FALSE(null_type->impl->SatisfiesType(*mgp_list_v)) + << null_type->impl->GetPresentableName(); + EXPECT_FALSE(null_type->impl->SatisfiesType(tv_list)); + } +}