diff --git a/CMakeLists.txt b/CMakeLists.txt index f794e975b..789f7b5df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -309,6 +309,8 @@ set(memgraph_src_files ${src_dir}/storage/label/labels_writer.cpp ${src_dir}/storage/edge_type/edge_type.cpp ${src_dir}/storage/edge_type/edge_type_store.cpp + ${src_dir}/storage/model/typed_value.cpp + ${src_dir}/storage/model/typed_value_store.cpp ${src_dir}/storage/model/properties/null.cpp ${src_dir}/storage/model/properties/bool.cpp ${src_dir}/storage/model/properties/int32.cpp diff --git a/include/storage/model/typed_value.hpp b/include/storage/model/typed_value.hpp new file mode 100644 index 000000000..4a0aaf296 --- /dev/null +++ b/include/storage/model/typed_value.hpp @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "utils/underlying_cast.hpp" +#include "utils/total_ordering.hpp" +#include "utils/exceptions/basic_exception.hpp" + +/** + * Encapsulation of a value and it's type encapsulated in a class that has no + * compiled-time info about that type. + * + * Values can be of a number of predefined types that are enumerated in + * TypedValue::Type. Each such type corresponds to exactly one C++ type. + */ +class TypedValue : public TotalOrdering { + +private: + /** Private default constructor, makes Null */ + TypedValue() : type_(Type::Null) {} + +public: + + /** A value type. Each type corresponds to exactly one C++ type */ + enum class Type : unsigned { + Null, + String, + Bool, + Int, + Float + }; + + // single static reference to Null, used whenever Null should be returned + static const TypedValue Null; + + // constructors for primitive types + TypedValue(bool value) : type_(Type::Bool) { bool_v = value; } + TypedValue(int value) : type_(Type::Int) { int_v = value; } + TypedValue(float value) : type_(Type::Float) { float_v = value; } + + /// constructors for non-primitive types (shared pointers) + TypedValue(const std::string &value) : type_(Type::String) { + new (&string_v) std::shared_ptr(new std::string(value)); + } + TypedValue(const char* value) : type_(Type::String) { + new (&string_v) std::shared_ptr(new std::string(value)); + } + + // assignment ops + TypedValue& operator=(TypedValue& other); + TypedValue& operator=(TypedValue&& other); + + TypedValue(const TypedValue& other); + ~TypedValue(); + + /** + * Returns the value of the property as given type T. + * The behavior of this function is undefined if + * T does not correspond to this property's type_. + * + * @tparam T Type to interpret the value as. + * @return The value as type T. + */ + template T Value() const; + + friend std::ostream& operator<<(std::ostream& stream, const TypedValue& prop); + + /** + * The Type of property. + */ + const Type type_; + +private: + // storage for the value of the property + union { + bool bool_v; + int int_v; + float float_v; + std::shared_ptr string_v; + }; +}; + +/** + * An exception raised by the TypedValue system. Typically when + * trying to perform operations (such as addition) on TypedValues + * of incompatible Types. + */ +class TypedValueException : public BasicException { + +public: + using ::BasicException::BasicException; +}; + +// comparison operators +// they return TypedValue because Null can be returned +TypedValue operator==(const TypedValue& a, const TypedValue& b); +TypedValue operator<(const TypedValue& a, const TypedValue& b); +TypedValue operator!(const TypedValue& a); + +// arithmetic operators +TypedValue operator-(const TypedValue& a); +TypedValue operator+(const TypedValue& a, const TypedValue& b); +TypedValue operator-(const TypedValue& a, const TypedValue& b); +TypedValue operator/(const TypedValue& a, const TypedValue& b); +TypedValue operator*(const TypedValue& a, const TypedValue& b); +TypedValue operator%(const TypedValue& a, const TypedValue& b); + +// binary bool operators +TypedValue operator&&(const TypedValue& a, const TypedValue& b); +TypedValue operator||(const TypedValue& a, const TypedValue& b); + +// stream output +std::ostream &operator<<(std::ostream &os, const TypedValue::Type type); diff --git a/include/storage/model/typed_value_store.hpp b/include/storage/model/typed_value_store.hpp new file mode 100644 index 000000000..efaf00be7 --- /dev/null +++ b/include/storage/model/typed_value_store.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +#include "typed_value.hpp" + +/** + * A collection of properties accessed in a map-like way + * using a key of type Properties::TKey. + * + * The underlying implementation is not necessarily std::map. + */ +class TypedValueStore { +public: + using sptr = std::shared_ptr; + + /** The type of key used to get and set properties */ + using TKey = uint32_t; + + /** + * Returns a TypedValue (by reference) at the given key. + * If the key does not exist, the Null property is returned. + * + * This is NOT thread-safe, the reference might not be valid + * when used in a multithreaded scenario. + * + * @param key The key for which a TypedValue is sought. + * @return See above. + */ + const TypedValue &at(const TKey &key) const; + + /** + * Sets the value for the given key. A new TypedValue instance + * is created for the given value (which is of raw type). + * + * @tparam TValue Type of value. It must be one of the + * predefined possible TypedValue values (bool, string, int...) + * @param key The key for which the property is set. The previous + * value at the same key (if there was one) is replaced. + * @param value The value to set. + */ + template + void set(const TKey &key, const TValue &value); + + /** + * Set overriding for character constants. Forces conversion + * to std::string, otherwise templating might cast the pointer + * to something else (bool) and mess things up. + * + * @param key The key for which the property is set. The previous + * value at the same key (if there was one) is replaced. + * @param value The value to set. + */ + void set(const TKey &key, const char *value); + + /** + * Removes the TypedValue for the given key. + * + * @param key The key for which to remove the property. + * @return The number of removed properties (0 or 1). + */ + size_t erase(const TKey &key); + + /** + * @return The number of Properties in this collection. + */ + size_t size() const; + + + /** + * Accepts two functions. + * + * @param handler Called for each TypedValue in this collection. + * @param finish Called once in the end. + */ + void Accept(std::function handler, + std::function finish = {}) const; + +private: + std::vector> props_; +}; diff --git a/include/storage/model/typed_value_utils.h b/include/storage/model/typed_value_utils.h new file mode 100644 index 000000000..5b5af377f --- /dev/null +++ b/include/storage/model/typed_value_utils.h @@ -0,0 +1,57 @@ +// +// Copyright 2017 Memgraph +// Created by Florijan Stamenkovic on 01.02.17. +// + +#pragma once + +#include +#include "storage/model/typed_value_store.hpp" + + +/** + * Writes all of the values from the given store in JSON format + * to the given output stream. + * + * @param store The store that should be serialized to JSON. + * @param ostream The stream to write to. + */ +void TypedValuesToJson(const TypedValueStore& store, + std::ostream& ostream=std::cout) { + + bool first = true; + + auto write_key = [&ostream, &first](const TypedValueStore::TKey &key) -> std::ostream& { + if (first) { + ostream << '{'; + first = false; + } else + ostream << ','; + + return ostream << '"' << key << "\":"; + }; + + auto handler = [&ostream, &write_key](const TypedValueStore::TKey& key, + const TypedValue& value) { + switch (value.type_) { + case TypedValue::Type::Null: + break; + case TypedValue::Type::Bool: + write_key(key) << (value.Value() ? "true" : "false"); + break; + case TypedValue::Type::String: + write_key(key) << '"' << value.Value() << '"'; + break; + case TypedValue::Type::Int: + write_key(key) << value.Value(); + break; + case TypedValue::Type::Float: + write_key(key) << value.Value(); + break; + } + }; + + auto finish = [&ostream]() { ostream << '}' << std::endl; }; + + store.Accept(handler, finish); +} diff --git a/include/utils/assert.hpp b/include/utils/assert.hpp index ac87544ad..842f183d2 100644 --- a/include/utils/assert.hpp +++ b/include/utils/assert.hpp @@ -41,6 +41,11 @@ std::exit(EXIT_FAILURE); \ } +#define permanent_fail(message) \ + std::ostringstream s; \ + s << message; \ + __handle_assert_message(s.str()); \ + std::exit(EXIT_FAILURE); \ /** * runtime assertion is more like standart C assert but with custom * define which controls when the assertion will be active diff --git a/include/utils/exceptions/basic_exception.hpp b/include/utils/exceptions/basic_exception.hpp index 9a30e854b..679391e51 100644 --- a/include/utils/exceptions/basic_exception.hpp +++ b/include/utils/exceptions/basic_exception.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "utils/auto_scope.hpp" @@ -21,6 +22,12 @@ public: { } + template + BasicException(const char* format, Args &&... args) noexcept + : BasicException(fmt::format(std::string(format), std::forward(args)...)) + { + } + const char *what() const noexcept override { return message_.c_str(); } private: diff --git a/include/utils/total_ordering.hpp b/include/utils/total_ordering.hpp index 4200fc046..8dd4ca785 100644 --- a/include/utils/total_ordering.hpp +++ b/include/utils/total_ordering.hpp @@ -1,25 +1,29 @@ #pragma once -template -struct TotalOrdering -{ - friend constexpr bool operator!=(const Derived &a, const Other &b) - { +/** + * Implements all the logical comparison operators based on '==' + * and '<' operators. + * + * @tparam TLhs First operand type. + * @tparam TRhs Second operand type. Defaults to the same type + * as first operand. + * @tparam TReturn Return type, defaults to bool. + */ +template +struct TotalOrdering { + friend constexpr TReturn operator!=(const TLhs &a, const TRhs &b) { return !(a == b); } - friend constexpr bool operator<=(const Derived &a, const Other &b) - { + friend constexpr TReturn operator<=(const TLhs &a, const TRhs &b) { return a < b || a == b; } - friend constexpr bool operator>(const Derived &a, const Other &b) - { + friend constexpr TReturn operator>(const TLhs &a, const TRhs &b) { return !(a <= b); } - friend constexpr bool operator>=(const Derived &a, const Other &b) - { + friend constexpr TReturn operator>=(const TLhs &a, const TRhs &b) { return !(a < b); } }; diff --git a/src/storage/model/typed_value.cpp b/src/storage/model/typed_value.cpp new file mode 100644 index 000000000..eeb4a39fe --- /dev/null +++ b/src/storage/model/typed_value.cpp @@ -0,0 +1,363 @@ +#include +#include +#include +#include + +#include "storage/model/typed_value.hpp" +#include "utils/assert.hpp" + +// Value extraction template instantiations +template<> +bool TypedValue::Value() const { + runtime_assert(type_ == TypedValue::Type::Bool, "Incompatible template param and type"); + return bool_v; +} + +template<> +std::string TypedValue::Value() const { + runtime_assert(type_ == TypedValue::Type::String, "Incompatible template param and type"); + return *string_v; +} + +template<> +int TypedValue::Value() const { + runtime_assert(type_ == TypedValue::Type::Int, "Incompatible template param and type"); + return int_v; +} + +template<> +float TypedValue::Value() const { + runtime_assert(type_ == TypedValue::Type::Float, "Incompatible template param and type"); + return float_v; +} + +TypedValue::TypedValue(const TypedValue &other) : type_(other.type_) { + switch (other.type_) { + case TypedValue::Type::Null: + return; + + case TypedValue::Type::Bool: + this->bool_v = other.bool_v; + return; + + case TypedValue::Type::String: + new(&string_v) std::shared_ptr(other.string_v); + return; + + case Type::Int: + this->int_v = other.int_v; + return; + + case Type::Float: + this->float_v = other.float_v; + return; + } + + permanent_fail("Unsupported TypedValue::Type"); +} + +std::ostream &operator<<(std::ostream &os, const TypedValue::Type type) { + switch (type) { + case TypedValue::Type::Null: + return os << "null"; + case TypedValue::Type::Bool: + return os << "bool"; + case TypedValue::Type::String: + return os << "string"; + case TypedValue::Type::Int: + return os << "int"; + case TypedValue::Type::Float: + return os << "float"; + } + permanent_fail("Unsupported TypedValue::Type"); +} + +std::ostream &operator<<(std::ostream &os, const TypedValue &property) { + switch (property.type_) { + case TypedValue::Type::Null: + return os << "Null"; + case TypedValue::Type::Bool: + return os << (property.Value() ? "true" : "false"); + case TypedValue::Type::String: + return os << property.Value(); + case TypedValue::Type::Int: + return os << property.Value(); + case TypedValue::Type::Float: + return os << property.Value(); + } + permanent_fail("Unsupported TypedValue::Type"); +} + +TypedValue &TypedValue::operator=(TypedValue &&other) { + + // set the type of this + const_cast(type_) = other.type_; + + if (this != &other) { + switch (other.type_) { + case TypedValue::Type::Null: + case TypedValue::Type::Bool: + this->bool_v = other.bool_v; + return *this; + case TypedValue::Type::String: + this->string_v = std::move(other.string_v); + return *this; + case TypedValue::Type::Int: + this->int_v = other.int_v; + return *this; + case TypedValue::Type::Float: + this->float_v = other.float_v; + return *this; + } + } + permanent_fail("Unsupported TypedValue::Type"); +} + +const TypedValue TypedValue::Null = TypedValue(); + +TypedValue::~TypedValue() { + + switch (type_) { + // destructor for primitive types does nothing + case Type::Null: + case Type::Bool: + case Type::Int: + case Type::Float: + return; + + // destructor for shared pointer must release + case Type::String: + string_v.~shared_ptr(); + return; + } + permanent_fail("Unsupported TypedValue::Type"); +} + +/** + * Returns the float value of a property. + * The property MUST be either Float or Int. + * + * @param prop + * @return + */ +float ToFloat(const TypedValue& prop) { + switch (prop.type_) { + case TypedValue::Type::Int: + return (float)prop.Value(); + case TypedValue::Type::Float: + return prop.Value(); + + default: + permanent_fail("Unsupported TypedValue::Type"); + } +} + +TypedValue operator<(const TypedValue& a, const TypedValue& b) { + if (a.type_ == TypedValue::Type::Bool || b.type_ == TypedValue::Type::Bool) + throw TypedValueException("Invalid 'less' operand types({} + {})", a.type_, b.type_); + + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + + if (a.type_ == TypedValue::Type::String || b.type_ == TypedValue::Type::String) { + if (a.type_ != b.type_) + throw TypedValueException("Invalid equality operand types({} + {})", a.type_, b.type_); + else + return a.Value() < b.Value(); + } + + // at this point we only have int and float + if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float) + return ToFloat(a) < ToFloat(b); + else + return a.Value() < b.Value(); +} + +TypedValue operator==(const TypedValue& a, const TypedValue& b) { + + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + + if (a.type_ == TypedValue::Type::String || b.type_ == TypedValue::Type::String) { + if (a.type_ != b.type_) + throw TypedValueException("Invalid equality operand types({} + {})", a.type_, b.type_); + else + return a.Value() == b.Value(); + } + + if (a.type_ == TypedValue::Type::Bool || b.type_ == TypedValue::Type::Bool) { + if (a.type_ != b.type_) + throw TypedValueException("Invalid equality operand types({} + {})", a.type_, b.type_); + else + return a.Value() == b.Value(); + } + // at this point we only have int and float + if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){ + return ToFloat(a) == ToFloat(b); + } else + return a.Value() == b.Value(); + +} + +TypedValue operator!(const TypedValue& a) { + switch (a.type_) { + case TypedValue::Type::Null: + return TypedValue::Null; + case TypedValue::Type::Bool: + return TypedValue(!a.Value()); + + default: + throw TypedValueException("Invalid logical not operand type (!{})", a.type_); + } +} + +/** + * Turns a numeric or string property into a string. + * + * @param prop Property. + * @return A string. + */ +std::string PropToString(const TypedValue& prop) { + switch (prop.type_) { + case TypedValue::Type::String: + return prop.Value(); + case TypedValue::Type::Int: + return std::to_string(prop.Value()); + case TypedValue::Type::Float: + return fmt::format("{}", prop.Value()); + + // unsupported situations + default: + permanent_fail("Unsupported TypedValue::Type"); + } +} + +TypedValue operator-(const TypedValue &a) { + switch (a.type_) { + case TypedValue::Type::Null: + return TypedValue::Null; + case TypedValue::Type::Int: + return -a.Value(); + case TypedValue::Type::Float: + return -a.Value(); + + default: + throw TypedValueException("Invalid unary minus operand type (-{})", a.type_); + } +} + +/** + * Raises a PropertyException if the given properties do not support arithmetic + * operations. If they do, nothing happens. + * + * @param a First prop. + * @param b Second prop. + * @param string_ok If or not for the given operation it's valid to work with + * String values (typically it's OK only for sum). + * @param op_name Name of the operation, used only for exception description, + * if raised. + */ +inline void EnsureArithmeticallyOk(const TypedValue& a, const TypedValue& b, + bool string_ok, const std::string& op_name) { + if (a.type_ == TypedValue::Type::Bool || b.type_ == TypedValue::Type::Bool) + throw TypedValueException("Invalid {} operand types {}, {}", op_name, a.type_, b.type_); + + if (string_ok) + return; + + if (a.type_ == TypedValue::Type::String || b.type_ == TypedValue::Type::String) + throw TypedValueException("Invalid subtraction operands types {}, {}", a.type_, b.type_); +} + +TypedValue operator+(const TypedValue& a, const TypedValue& b){ + EnsureArithmeticallyOk(a, b, true, "addition"); + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + + // no more Bool nor Null, summing works on anything from here onward + + if (a.type_ == TypedValue::Type::String || b.type_ == TypedValue::Type::String) + return PropToString(a) + PropToString(b); + + // at this point we only have int and float + if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){ + return ToFloat(a) + ToFloat(b); + } else + return a.Value() + b.Value(); +} + +TypedValue operator-(const TypedValue& a, const TypedValue& b){ + EnsureArithmeticallyOk(a, b, false, "subtraction"); + + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + + // at this point we only have int and float + if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){ + return ToFloat(a) - ToFloat(b); + } else + return a.Value() - b.Value(); +} + +TypedValue operator/(const TypedValue& a, const TypedValue& b){ + EnsureArithmeticallyOk(a, b, false, "division"); + + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + + // at this point we only have int and float + if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){ + return ToFloat(a) / ToFloat(b); + } else + return a.Value() / b.Value(); +} + +TypedValue operator*(const TypedValue& a, const TypedValue& b){ + EnsureArithmeticallyOk(a, b, false, "multiplication"); + + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + + // at this point we only have int and float + if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){ + return ToFloat(a) * ToFloat(b); + } else + return a.Value() * b.Value(); +} + +TypedValue operator%(const TypedValue& a, const TypedValue& b){ + EnsureArithmeticallyOk(a, b, false, "modulo"); + + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + + // at this point we only have int and float + if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){ + return (float)fmod(ToFloat(a), ToFloat(b)); + } else + return a.Value() % b.Value(); +} + +inline bool IsLogicallyOk(const TypedValue& a) { + return a.type_ == TypedValue::Type::Bool || a.type_ == TypedValue::Type::Null; +} + +TypedValue operator&&(const TypedValue& a, const TypedValue& b) { + if(IsLogicallyOk(a) && IsLogicallyOk(b)){ + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + else + return a.Value() && b.Value(); + } else + throw TypedValueException("Invalid logical and operand types({} && {})", a.type_, b.type_); +} + +TypedValue operator||(const TypedValue& a, const TypedValue& b) { + if(IsLogicallyOk(a) && IsLogicallyOk(b)){ + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + else + return a.Value() || b.Value(); + } else + throw TypedValueException("Invalid logical and operand types({} && {})", a.type_, b.type_); +} diff --git a/src/storage/model/typed_value_store.cpp b/src/storage/model/typed_value_store.cpp new file mode 100644 index 000000000..9e538afb8 --- /dev/null +++ b/src/storage/model/typed_value_store.cpp @@ -0,0 +1,68 @@ +#include + +#include "storage/model/typed_value_store.hpp" + +const TypedValue& TypedValueStore::at(const TKey &key) const { + for (const auto& kv : props_) + if (kv.first == key) + return kv.second; + + return TypedValue::Null; +} + +template +void TypedValueStore::set(const TKey &key, const TValue &value) { + for (auto& kv: props_) + if (kv.first == key) { + kv.second = TypedValue(value); + return; + } + + // there is no value for the given key, add new + // TODO consider vector size increment optimization + props_.push_back(std::move(std::make_pair(key, value))); +} + +// instantiations of the TypedValueStore::set function +// instances must be made for all of the supported C++ types +template void TypedValueStore::set(const TKey &key, const std::string &value); +template void TypedValueStore::set(const TKey &key, const bool &value); +template void TypedValueStore::set(const TKey &key, const int &value); +template void TypedValueStore::set(const TKey &key, const float &value); + +/** + * Set overriding for character constants. Forces conversion + * to std::string, otherwise templating might cast the pointer + * to something else (bool) and mess things up. + * + * @param key The key for which the property is set. The previous + * value at the same key (if there was one) is replaced. + * @param value The value to set. + */ +void TypedValueStore::set(const TKey &key, const char *value) { + set(key, std::string(value)); +} + +size_t TypedValueStore::erase(const TKey &key) { + auto found = std::find_if(props_.begin(), props_.end(), [&key](std::pair &kv){return kv.first == key;}); + if (found != props_.end()) { + props_.erase(found); + return 1; + } + return 0; +} + +size_t TypedValueStore::size() const { return props_.size(); } + +void TypedValueStore::Accept(std::function handler, + std::function finish) const { + if (handler) + for (const auto& prop : props_) + handler(prop.first, prop.second); + + if (finish) + finish(); + +} + + diff --git a/tests/unit/typed_value.cpp b/tests/unit/typed_value.cpp new file mode 100644 index 000000000..d46a3c9fd --- /dev/null +++ b/tests/unit/typed_value.cpp @@ -0,0 +1,293 @@ +// +// Copyright 2017 Memgraph +// Created by Florijan Stamenkovic on 24.01.17.. +// +#include +#include + +#include "gtest/gtest.h" + +#include "storage/model/typed_value.hpp" +#include "storage/model/typed_value_store.hpp" + +TypedValueStore MakePropsAllTypes() { + TypedValueStore props; + props.set(0, true); + props.set(1, "something"); + props.set(2, 42); + props.set(3, 0.5f); + return props; +} + +void EXPECT_PROP_FALSE(const TypedValue& a) { + EXPECT_TRUE(a.type_ == TypedValue::Type::Bool && !a.Value()); +} + +void EXPECT_PROP_TRUE(const TypedValue& a) { + EXPECT_TRUE(a.type_ == TypedValue::Type::Bool && a.Value()); +} + +void EXPECT_PROP_EQ(const TypedValue& a, const TypedValue& b) { + EXPECT_PROP_TRUE(a == b); +} + +void EXPECT_PROP_ISNULL(const TypedValue& a) { + EXPECT_TRUE(a.type_ == TypedValue::Type::Null); +} + +void EXPECT_PROP_NE(const TypedValue& a, const TypedValue& b) { + EXPECT_PROP_TRUE(a != b); +} + +TEST(TypedValue, CreationTypes) { + EXPECT_TRUE(TypedValue::Null.type_ == TypedValue::Type::Null); + + EXPECT_TRUE(TypedValue(true).type_ == TypedValue::Type::Bool); + EXPECT_TRUE(TypedValue(false).type_ == TypedValue::Type::Bool); + + EXPECT_TRUE(TypedValue(std::string("form string class")).type_ == TypedValue::Type::String); + EXPECT_TRUE(TypedValue("form c-string").type_ == TypedValue::Type::String); + + EXPECT_TRUE(TypedValue(0).type_ == TypedValue::Type::Int); + EXPECT_TRUE(TypedValue(42).type_ == TypedValue::Type::Int); + + EXPECT_TRUE(TypedValue(0.0f).type_ == TypedValue::Type::Float); + EXPECT_TRUE(TypedValue(42.5f).type_ == TypedValue::Type::Float); +} + +TEST(TypedValue, CreationValues) { + EXPECT_EQ(TypedValue(true).Value(), true); + EXPECT_EQ(TypedValue(false).Value(), false); + + EXPECT_EQ(TypedValue(std::string("bla")).Value(), "bla"); + EXPECT_EQ(TypedValue("bla2").Value(), "bla2"); + + EXPECT_EQ(TypedValue(55).Value(), 55); + + EXPECT_FLOAT_EQ(TypedValue(66.6f).Value(), 66.6f); +} + +TEST(TypedValue, Equals) { + EXPECT_PROP_EQ(TypedValue(true), TypedValue(true)); + EXPECT_PROP_NE(TypedValue(true), TypedValue(false)); + + EXPECT_PROP_EQ(TypedValue(42), TypedValue(42)); + EXPECT_PROP_NE(TypedValue(0), TypedValue(1)); + + EXPECT_PROP_NE(TypedValue(0.5f), TypedValue(0.12f)); + EXPECT_PROP_EQ(TypedValue(0.123f), TypedValue(0.123f)); + + EXPECT_PROP_EQ(TypedValue(2), TypedValue(2.0f)); + EXPECT_PROP_NE(TypedValue(2), TypedValue(2.1f)); + + EXPECT_PROP_NE(TypedValue("str1"), TypedValue("str2")); + EXPECT_PROP_EQ(TypedValue("str3"), TypedValue("str3")); + EXPECT_PROP_EQ(TypedValue(std::string("str3")), TypedValue("str3")); +} + +TEST(TypedValue, Less) { + // not_bool_type < bool -> exception + TypedValueStore props = MakePropsAllTypes(); + for (int i = 0; i < props.size() + 1; ++i) { + if (props.at(i).type_ == TypedValue::Type::Bool) + continue; + // the comparison should raise an exception + // cast to (void) so the compiler does not complain about unused comparison result + EXPECT_THROW((void)(props.at(i) < TypedValue(true)), TypedValueException); + } + + // not_bool_type < Null = Null + props = MakePropsAllTypes(); + for (int i = 0; i < props.size() + 1; ++i) { + if (props.at(i).type_ == TypedValue::Type::Bool) + continue; + EXPECT_PROP_ISNULL(props.at(i) < TypedValue::Null); + } + + // int tests + EXPECT_PROP_TRUE(TypedValue(2) < TypedValue(3)); + EXPECT_PROP_FALSE(TypedValue(2) < TypedValue(2)); + EXPECT_PROP_FALSE(TypedValue(3) < TypedValue(2)); + + // float tests + EXPECT_PROP_TRUE(TypedValue(2.1f) < TypedValue(2.5f)); + EXPECT_PROP_FALSE(TypedValue(2.0f) < TypedValue(2.0f)); + EXPECT_PROP_FALSE(TypedValue(2.5f) < TypedValue(2.4f)); + + // implicit casting int->float + EXPECT_PROP_TRUE(TypedValue(2) < TypedValue(2.1f)); + EXPECT_PROP_FALSE(TypedValue(2.1f) < TypedValue(2)); + EXPECT_PROP_FALSE(TypedValue(2) < TypedValue(1.5f)); + EXPECT_PROP_TRUE(TypedValue(1.5f) < TypedValue(2)); + + // string tests + EXPECT_PROP_TRUE(TypedValue("a") < TypedValue("b")); + EXPECT_PROP_TRUE(TypedValue("aaaaa") < TypedValue("b")); + EXPECT_PROP_TRUE(TypedValue("A") < TypedValue("a")); +} + +TEST(TypedValue, LogicalNot) { + EXPECT_PROP_EQ(!TypedValue(true), TypedValue(false)); + EXPECT_PROP_ISNULL(!TypedValue::Null); + EXPECT_THROW(!TypedValue(0), TypedValueException); + EXPECT_THROW(!TypedValue(0.2f), TypedValueException); + EXPECT_THROW(!TypedValue("something"), TypedValueException); +} + +TEST(TypedValue, UnaryMinus) { + EXPECT_TRUE((-TypedValue::Null).type_ == TypedValue::Type::Null); + + EXPECT_PROP_EQ((-TypedValue(2).Value()), -2); + EXPECT_FLOAT_EQ((-TypedValue(2.0f).Value()), -2.0f); + + EXPECT_THROW(-TypedValue(true), TypedValueException); + EXPECT_THROW(-TypedValue("something"), TypedValueException); +} + + +/** + * Performs a series of tests on properties of all types. The tests + * evaluate how arithmetic operators behave w.r.t. exception throwing + * in case of invalid operands and null handling. + * + * @param string_ok Indicates if or not the operation tested works + * with String values (does not throw). + * @param op The operation lambda. Takes two values and resturns + * the results. + */ +void ExpectArithmeticThrowsAndNull(bool string_ok, std::function op) { + // arithmetic ops that raise + TypedValueStore props = MakePropsAllTypes(); + for (int i = 0; i < props.size() + 1; ++i) { + EXPECT_THROW(op(TypedValue(true), props.at(i)), TypedValueException); + EXPECT_THROW(op(props.at(i), TypedValue(true)), TypedValueException); + if (!string_ok) { + EXPECT_THROW(op(TypedValue("some"), props.at(i)), TypedValueException); + EXPECT_THROW(op(props.at(i), TypedValue("some")), TypedValueException); + } + } + + // null resulting ops + props = MakePropsAllTypes(); + for (int i = 0; i < props.size() + 1; ++i) { + if (props.at(i).type_ == TypedValue::Type::Bool) + continue; + if (!string_ok && props.at(i).type_ == TypedValue::Type::String) + continue; + + EXPECT_PROP_ISNULL(op(props.at(i), TypedValue::Null)); + EXPECT_PROP_ISNULL(op(TypedValue::Null, props.at(i))); + } +} + +TEST(TypedValue, Sum) { + ExpectArithmeticThrowsAndNull(true, [](const TypedValue &a, const TypedValue& b) { return a + b; }); + + // sum of props of the same type + EXPECT_EQ((TypedValue(2) + TypedValue(3)).Value(), 5); + EXPECT_FLOAT_EQ((TypedValue(2.5f) + TypedValue(1.25f)).Value(), 3.75); + EXPECT_EQ((TypedValue("one") + TypedValue("two")).Value(), "onetwo"); + + // sum of string and numbers + EXPECT_EQ((TypedValue("one") + TypedValue(1)).Value(), "one1"); + EXPECT_EQ((TypedValue(1) + TypedValue("one")).Value(), "1one"); + EXPECT_EQ((TypedValue("one") + TypedValue(3.2f)).Value(), "one3.2"); + EXPECT_EQ((TypedValue(3.2f) + TypedValue("one")).Value(), "3.2one"); +} + +TEST(TypedValue, Difference) { + ExpectArithmeticThrowsAndNull(false, [](const TypedValue &a, const TypedValue& b) { return a - b; }); + + // difference of props of the same type + EXPECT_EQ((TypedValue(2) - TypedValue(3)).Value(), -1); + EXPECT_FLOAT_EQ((TypedValue(2.5f) - TypedValue(2.25f)).Value(), 0.25); + + // implicit casting + EXPECT_FLOAT_EQ((TypedValue(2) - TypedValue(0.5f)).Value(), 1.5f); + EXPECT_FLOAT_EQ((TypedValue(2.5f) - TypedValue(2)).Value(), 0.5f); +} + +TEST(TypedValue, Divison) { + ExpectArithmeticThrowsAndNull(false, [](const TypedValue &a, const TypedValue& b) { return a / b; }); + + EXPECT_PROP_EQ(TypedValue(10) / TypedValue(2), TypedValue(5)); + EXPECT_PROP_EQ(TypedValue(10) / TypedValue(4), TypedValue(2)); + + EXPECT_PROP_EQ(TypedValue(10.0f) / TypedValue(2.0f), TypedValue(5.0f)); + EXPECT_FLOAT_EQ((TypedValue(10.0f) / TypedValue(4.0f)).Value(), 2.5f); + + EXPECT_FLOAT_EQ((TypedValue(10) / TypedValue(4.0f)).Value(), 2.5f); + EXPECT_FLOAT_EQ((TypedValue(10.0f) / TypedValue(4)).Value(), 2.5f); +} + +TEST(TypedValue, Multiplication) { + ExpectArithmeticThrowsAndNull(false, [](const TypedValue &a, const TypedValue& b) { return a * b; }); + + EXPECT_PROP_EQ(TypedValue(10) * TypedValue(2), TypedValue(20)); + EXPECT_FLOAT_EQ((TypedValue(12.5f) * TypedValue(6.6f)).Value(), 12.5f * 6.6f); + EXPECT_FLOAT_EQ((TypedValue(10) * TypedValue(4.5f)).Value(), 10 * 4.5f); + EXPECT_FLOAT_EQ((TypedValue(10.2f) * TypedValue(4)).Value(), 10.2f * 4); +} + +TEST(TypedValue, Modulo) { + ExpectArithmeticThrowsAndNull(false, [](const TypedValue &a, const TypedValue& b) { return a % b; }); + + EXPECT_PROP_EQ(TypedValue(10) % TypedValue(2), TypedValue(0)); + EXPECT_PROP_EQ(TypedValue(10) % TypedValue(4), TypedValue(2)); + + EXPECT_PROP_EQ(TypedValue(10.0f) % TypedValue(2.0f), TypedValue(0.0f)); + EXPECT_FLOAT_EQ((TypedValue(10.0f) % TypedValue(3.25f)).Value(), 0.25f); + + EXPECT_FLOAT_EQ((TypedValue(10) % TypedValue(4.0f)).Value(), 2.0f); + EXPECT_FLOAT_EQ((TypedValue(10.0f) % TypedValue(4)).Value(), 2.0f); +} + +TEST(TypedValue, TypeIncompatibility) { + TypedValueStore props = MakePropsAllTypes(); + + // iterate over all the props, plus one, what will return + // the Null property, which must be incompatible with all + // the others + for (int i = 0; i < props.size() + 1; ++i) + for (int j = 0; j < props.size() + 1; ++j) + EXPECT_EQ(props.at(i).type_== props.at(j).type_, i == j); +} + +/** + * Logical operations (logical and, or) are only legal on bools + * and nulls. This function ensures that the given + * logical operation throws exceptions when either operand + * is not bool or null. + * + * @param op The logical operation to test. + */ +void TestLogicalThrows(std::function op) { + TypedValueStore props = MakePropsAllTypes(); + for (int i = 0; i < props.size() + 1; ++i) { + auto p1 = props.at(i); + for (int j = 0; j < props.size() + 1; ++j) { + auto p2 = props.at(j); + // skip situations when both p1 and p2 are either bool or null + auto p1_ok = p1.type_ == TypedValue::Type::Bool || p1.type_ == TypedValue::Type::Null; + auto p2_ok = p2.type_ == TypedValue::Type::Bool || p2.type_ == TypedValue::Type::Null; + if (p1_ok && p2_ok) + continue; + + EXPECT_THROW(op(p1, p2), TypedValueException); + } + } +} + +TEST(TypedValue, LogicalAnd) { + TestLogicalThrows([](const TypedValue& p1, const TypedValue& p2) {return p1 && p2;}); + EXPECT_PROP_ISNULL(TypedValue::Null && TypedValue(true)); + EXPECT_PROP_EQ(TypedValue(true) && TypedValue(true), TypedValue(true)); + EXPECT_PROP_EQ(TypedValue(false) && TypedValue(true), TypedValue(false)); +} + +TEST(TypedValue, LogicalOr) { + TestLogicalThrows([](const TypedValue& p1, const TypedValue& p2) {return p1 || p2;}); + EXPECT_PROP_ISNULL(TypedValue::Null && TypedValue(true)); + EXPECT_PROP_EQ(TypedValue(true) || TypedValue(true), TypedValue(true)); + EXPECT_PROP_EQ(TypedValue(false) || TypedValue(true), TypedValue(true)); +} diff --git a/tests/unit/typed_value_store.cpp b/tests/unit/typed_value_store.cpp new file mode 100644 index 000000000..32495972b --- /dev/null +++ b/tests/unit/typed_value_store.cpp @@ -0,0 +1,117 @@ +// +// Copyright 2017 Memgraph +// Created by Florijan Stamenkovic on 24.01.17.. +// +#include + +#include "gtest/gtest.h" + +#include "storage/model/typed_value.hpp" +#include "storage/model/typed_value_store.hpp" + +using std::string; + +TEST(TypedValueStore, At) { + + std::string some_string = "something"; + + TypedValueStore props; + EXPECT_EQ(TypedValue(props.at(0)).type_, TypedValue::Type::Null); + props.set(0, some_string); + EXPECT_EQ(TypedValue(props.at(0)).Value(), some_string); + props.set(120, 42); + EXPECT_EQ(TypedValue(props.at(120)).Value(), 42); +} + +TEST(TypedValueStore, AtNull) { + TypedValueStore props; + EXPECT_EQ(props.at(0).type_, TypedValue::Type::Null); + EXPECT_EQ(props.at(100).type_, TypedValue::Type::Null); + + // set one prop and test it's not null + props.set(0, true); + EXPECT_NE(props.at(0).type_, TypedValue::Type::Null); + EXPECT_EQ(props.at(100).type_, TypedValue::Type::Null); +} + +TEST(TypedValueStore, Remove) { + // set some props + TypedValueStore props; + props.set(11, "a"); + props.set(30, "b"); + EXPECT_NE(props.at(11).type_, TypedValue::Type::Null); + EXPECT_NE(props.at(30).type_, TypedValue::Type::Null); + EXPECT_EQ(props.size(), 2); + + props.erase(11); + EXPECT_EQ(props.size(), 1); + EXPECT_EQ(props.at(11).type_, TypedValue::Type::Null); + + EXPECT_EQ(props.erase(30), 1); + EXPECT_EQ(props.size(), 0); + EXPECT_EQ(props.at(30).type_, TypedValue::Type::Null); + + EXPECT_EQ(props.erase(1000), 0); +} + +TEST(TypedValueStore, Replace) { + TypedValueStore props; + props.set(10, 42); + EXPECT_EQ(props.at(10).Value(), 42); + props.set(10, 0.25f); + EXPECT_EQ(props.at(10).type_, TypedValue::Type::Float); + EXPECT_FLOAT_EQ(props.at(10).Value(), 0.25); +} + +TEST(TypedValueStore, Size) { + + TypedValueStore props; + EXPECT_EQ(props.size(), 0); + + props.set(0, "something"); + EXPECT_EQ(props.size(), 1); + props.set(0, true); + EXPECT_EQ(props.size(), 1); + props.set(1, true); + EXPECT_EQ(props.size(), 2); + + for (int i = 0; i < 100; ++i) + props.set(i + 20, true); + EXPECT_EQ(props.size(), 102); + + props.erase(0); + EXPECT_EQ(props.size(), 101); + props.erase(0); + EXPECT_EQ(props.size(), 101); + props.erase(1); + EXPECT_EQ(props.size(), 100); +} + +TEST(TypedValueStore, Accept) { + + int count_props = 0; + int count_finish = 0; + + auto handler = [&](const TypedValueStore::TKey key, const TypedValue& prop) { + count_props += 1; + }; + auto finish = [&]() { + count_finish += 1; + }; + + TypedValueStore props; + props.Accept(handler, finish); + EXPECT_EQ(count_props, 0); + EXPECT_EQ(count_finish, 1); + + props.Accept(handler); + EXPECT_EQ(count_props, 0); + EXPECT_EQ(count_finish, 1); + + props.set(0, 20); + props.set(1, "bla"); + + props.Accept(handler, finish); + EXPECT_EQ(count_props, 2); + EXPECT_EQ(count_finish, 2); +}