Implement SatisfiesType on CypherType classes

Reviewers: mferencevic, ipaljak

Reviewed By: mferencevic

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2571
This commit is contained in:
Teon Banek 2019-11-26 12:46:29 +01:00
parent c5def558e8
commit e1399582b8
2 changed files with 432 additions and 7 deletions

View File

@ -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; }
};

View File

@ -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));
}
}