diff --git a/src/storage/v2/property_value.hpp b/src/storage/v2/property_value.hpp new file mode 100644 index 000000000..e8655d6d1 --- /dev/null +++ b/src/storage/v2/property_value.hpp @@ -0,0 +1,398 @@ +#pragma once + +#include <iostream> +#include <map> +#include <string> +#include <vector> + +#include "utils/algorithm.hpp" +#include "utils/exceptions.hpp" + +namespace storage { + +/// An exception raised by the PropertyValue. Typically when trying to perform +/// operations (such as addition) on PropertyValues of incompatible Types. +class PropertyValueException : public utils::BasicException { + public: + using utils::BasicException::BasicException; +}; + +/// Encapsulation of a value and its type in a class that has no compile-time +/// info about the type. +/// +/// Values can be of a number of predefined types that are enumerated in +/// PropertyValue::Type. Each such type corresponds to exactly one C++ type. +class PropertyValue { + public: + enum class Type : uint8_t { Null, Bool, Int, Double, String, List, Map }; + + // default constructor, makes Null + PropertyValue() : type_(Type::Null) {} + + // constructors for primitive types + explicit PropertyValue(bool value) : type_(Type::Bool) { bool_v = value; } + explicit PropertyValue(int value) : type_(Type::Int) { int_v = value; } + explicit PropertyValue(int64_t value) : type_(Type::Int) { int_v = value; } + explicit PropertyValue(double value) : type_(Type::Double) { + double_v = value; + } + + // copy constructors for non-primitive types + explicit PropertyValue(const std::string &value) : type_(Type::String) { + new (&string_v) std::string(value); + } + explicit PropertyValue(const char *value) : type_(Type::String) { + new (&string_v) std::string(value); + } + explicit PropertyValue(const std::vector<PropertyValue> &value) + : type_(Type::List) { + new (&list_v) std::vector<PropertyValue>(value); + } + explicit PropertyValue(const std::map<std::string, PropertyValue> &value) + : type_(Type::Map) { + new (&map_v) std::map<std::string, PropertyValue>(value); + } + + // move constructors for non-primitive types + explicit PropertyValue(std::string &&value) noexcept : type_(Type::String) { + new (&string_v) std::string(std::move(value)); + } + explicit PropertyValue(std::vector<PropertyValue> &&value) noexcept + : type_(Type::List) { + new (&list_v) std::vector<PropertyValue>(std::move(value)); + } + explicit PropertyValue(std::map<std::string, PropertyValue> &&value) noexcept + : type_(Type::Map) { + new (&map_v) std::map<std::string, PropertyValue>(std::move(value)); + } + + // copy constructor + PropertyValue(const PropertyValue &other) : type_(other.type_) { + switch (other.type_) { + case Type::Null: + return; + case Type::Bool: + this->bool_v = other.bool_v; + return; + case Type::Int: + this->int_v = other.int_v; + return; + case Type::Double: + this->double_v = other.double_v; + return; + case Type::String: + new (&string_v) std::string(other.string_v); + return; + case Type::List: + new (&list_v) std::vector<PropertyValue>(other.list_v); + return; + case Type::Map: + new (&map_v) std::map<std::string, PropertyValue>(other.map_v); + return; + } + } + + // move constructor + PropertyValue(PropertyValue &&other) noexcept : type_(other.type_) { + switch (other.type_) { + case Type::Null: + break; + case Type::Bool: + this->bool_v = other.bool_v; + break; + case Type::Int: + this->int_v = other.int_v; + break; + case Type::Double: + this->double_v = other.double_v; + break; + case Type::String: + new (&string_v) std::string(std::move(other.string_v)); + break; + case Type::List: + new (&list_v) std::vector<PropertyValue>(std::move(other.list_v)); + break; + case Type::Map: + new (&map_v) + std::map<std::string, PropertyValue>(std::move(other.map_v)); + break; + } + + // reset the type of other + other.DestroyValue(); + other.type_ = Type::Null; + } + + // copy assignment + PropertyValue &operator=(const PropertyValue &other) { + if (this == &other) return *this; + + DestroyValue(); + type_ = other.type_; + + switch (other.type_) { + case Type::Null: + break; + case Type::Bool: + this->bool_v = other.bool_v; + break; + case Type::Int: + this->int_v = other.int_v; + break; + case Type::Double: + this->double_v = other.double_v; + break; + case Type::String: + new (&string_v) std::string(other.string_v); + break; + case Type::List: + new (&list_v) std::vector<PropertyValue>(other.list_v); + break; + case Type::Map: + new (&map_v) std::map<std::string, PropertyValue>(other.map_v); + break; + } + + return *this; + } + + // move assignment + PropertyValue &operator=(PropertyValue &&other) noexcept { + if (this == &other) return *this; + + DestroyValue(); + type_ = other.type_; + + switch (other.type_) { + case Type::Null: + break; + case Type::Bool: + this->bool_v = other.bool_v; + break; + case Type::Int: + this->int_v = other.int_v; + break; + case Type::Double: + this->double_v = other.double_v; + break; + case Type::String: + new (&string_v) std::string(std::move(other.string_v)); + break; + case Type::List: + new (&list_v) std::vector<PropertyValue>(std::move(other.list_v)); + break; + case Type::Map: + new (&map_v) + std::map<std::string, PropertyValue>(std::move(other.map_v)); + break; + } + + // reset the type of other + other.DestroyValue(); + other.type_ = Type::Null; + + return *this; + } + + // TODO: Implement copy assignment operators for primitive types. + // TODO: Implement copy and move assignment operators for non-primitive types. + + ~PropertyValue() { DestroyValue(); } + + Type type() const { return type_; } + + // type checkers + bool IsNull() const { return type_ == Type::Null; } + bool IsBool() const { return type_ == Type::Bool; } + bool IsInt() const { return type_ == Type::Int; } + bool IsDouble() const { return type_ == Type::Double; } + bool IsString() const { return type_ == Type::String; } + bool IsList() const { return type_ == Type::List; } + bool IsMap() const { return type_ == Type::Map; } + + // value getters for primitive types + bool ValueBool() const { + if (type_ != Type::Bool) { + throw PropertyValueException("The value isn't a bool!"); + } + return bool_v; + } + int64_t ValueInt() const { + if (type_ != Type::Int) { + throw PropertyValueException("The value isn't an int!"); + } + return int_v; + } + double ValueDouble() const { + if (type_ != Type::Double) { + throw PropertyValueException("The value isn't a double!"); + } + return double_v; + } + + // const value getters for non-primitive types + const std::string &ValueString() const { + if (type_ != Type::String) { + throw PropertyValueException("The value isn't a string!"); + } + return string_v; + } + const std::vector<PropertyValue> &ValueList() const { + if (type_ != Type::List) { + throw PropertyValueException("The value isn't a list!"); + } + return list_v; + } + const std::map<std::string, PropertyValue> &ValueMap() const { + if (type_ != Type::Map) { + throw PropertyValueException("The value isn't a map!"); + } + return map_v; + } + + // reference value getters for non-primitive types + std::string &ValueString() { + if (type_ != Type::String) { + throw PropertyValueException("The value isn't a string!"); + } + return string_v; + } + std::vector<PropertyValue> &ValueList() { + if (type_ != Type::List) { + throw PropertyValueException("The value isn't a list!"); + } + return list_v; + } + std::map<std::string, PropertyValue> &ValueMap() { + if (type_ != Type::Map) { + throw PropertyValueException("The value isn't a map!"); + } + return map_v; + } + + private: + void DestroyValue() { + switch (type_) { + // destructor for primitive types does nothing + case Type::Null: + case Type::Bool: + case Type::Int: + case Type::Double: + return; + + // destructor for non primitive types since we used placement new + case Type::String: + // Clang fails to compile ~std::string. It seems it is a bug in some + // versions of clang. Using namespace std statement solves the issue. + using namespace std; + string_v.~string(); + return; + case Type::List: + list_v.~vector(); + return; + case Type::Map: + map_v.~map(); + return; + } + } + + union { + bool bool_v; + int64_t int_v; + double double_v; + std::string string_v; + std::vector<PropertyValue> list_v; + std::map<std::string, PropertyValue> map_v; + }; + + Type type_; +}; + +// stream output +inline std::ostream &operator<<(std::ostream &os, + const PropertyValue::Type type) { + switch (type) { + case PropertyValue::Type::Null: + return os << "null"; + case PropertyValue::Type::Bool: + return os << "bool"; + case PropertyValue::Type::Int: + return os << "int"; + case PropertyValue::Type::Double: + return os << "double"; + case PropertyValue::Type::String: + return os << "string"; + case PropertyValue::Type::List: + return os << "list"; + case PropertyValue::Type::Map: + return os << "map"; + } +} +inline std::ostream &operator<<(std::ostream &os, const PropertyValue &value) { + switch (value.type()) { + case PropertyValue::Type::Null: + return os << "null"; + case PropertyValue::Type::Bool: + return os << (value.ValueBool() ? "true" : "false"); + case PropertyValue::Type::Int: + return os << value.ValueInt(); + case PropertyValue::Type::Double: + return os << value.ValueDouble(); + case PropertyValue::Type::String: + return os << value.ValueString(); + case PropertyValue::Type::List: + os << "["; + utils::PrintIterable(os, value.ValueList()); + return os << "]"; + case PropertyValue::Type::Map: + os << "{"; + utils::PrintIterable(os, value.ValueMap(), ", ", + [](auto &stream, const auto &pair) { + stream << pair.first << ": " << pair.second; + }); + return os << "}"; + } +} + +// comparison +inline bool operator==(const PropertyValue &first, + const PropertyValue &second) { + if (first.type() != second.type()) return false; + switch (first.type()) { + case PropertyValue::Type::Null: + return true; + case PropertyValue::Type::Bool: + return first.ValueBool() == second.ValueBool(); + case PropertyValue::Type::Int: + return first.ValueInt() == second.ValueInt(); + case PropertyValue::Type::Double: + return first.ValueDouble() == second.ValueDouble(); + case PropertyValue::Type::String: + return first.ValueString() == second.ValueString(); + case PropertyValue::Type::List: + return first.ValueList() == second.ValueList(); + case PropertyValue::Type::Map: + return first.ValueMap() == second.ValueMap(); + } +} +inline bool operator<(const PropertyValue &first, const PropertyValue &second) { + if (first.type() != second.type()) return first.type() < second.type(); + switch (first.type()) { + case PropertyValue::Type::Null: + return false; + case PropertyValue::Type::Bool: + return first.ValueBool() < second.ValueBool(); + case PropertyValue::Type::Int: + return first.ValueInt() < second.ValueInt(); + case PropertyValue::Type::Double: + return first.ValueDouble() < second.ValueDouble(); + case PropertyValue::Type::String: + return first.ValueString() < second.ValueString(); + case PropertyValue::Type::List: + return first.ValueList() < second.ValueList(); + case PropertyValue::Type::Map: + return first.ValueMap() < second.ValueMap(); + } +} + +} // namespace storage diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 59adc6db6..6f5551ee1 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -379,6 +379,9 @@ target_link_libraries(${test_prefix}auth mg-auth kvstore_lib) # Test storage v2 +add_unit_test(property_value_v2.cpp) +target_link_libraries(${test_prefix}property_value_v2 mg-utils) + add_unit_test(storage_v2.cpp) target_link_libraries(${test_prefix}storage_v2 mg-utils) diff --git a/tests/unit/property_value_v2.cpp b/tests/unit/property_value_v2.cpp new file mode 100644 index 000000000..6aa93fbdf --- /dev/null +++ b/tests/unit/property_value_v2.cpp @@ -0,0 +1,731 @@ +#include <gtest/gtest.h> + +#include <sstream> + +#include "storage/v2/property_value.hpp" + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, Null) { + storage::PropertyValue pv; + + ASSERT_EQ(pv.type(), storage::PropertyValue::Type::Null); + + ASSERT_TRUE(pv.IsNull()); + ASSERT_FALSE(pv.IsBool()); + ASSERT_FALSE(pv.IsInt()); + ASSERT_FALSE(pv.IsDouble()); + ASSERT_FALSE(pv.IsString()); + ASSERT_FALSE(pv.IsList()); + ASSERT_FALSE(pv.IsMap()); + + ASSERT_THROW(pv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueDouble(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueString(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueList(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueMap(), storage::PropertyValueException); + + const auto &cpv = pv; + + ASSERT_THROW(cpv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueDouble(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueString(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueList(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueMap(), storage::PropertyValueException); + + { + std::stringstream ss; + ss << pv.type(); + ASSERT_EQ(ss.str(), "null"); + } + { + std::stringstream ss; + ss << pv; + ASSERT_EQ(ss.str(), "null"); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, Bool) { + storage::PropertyValue pv(false); + + ASSERT_EQ(pv.type(), storage::PropertyValue::Type::Bool); + + ASSERT_FALSE(pv.IsNull()); + ASSERT_TRUE(pv.IsBool()); + ASSERT_FALSE(pv.IsInt()); + ASSERT_FALSE(pv.IsDouble()); + ASSERT_FALSE(pv.IsString()); + ASSERT_FALSE(pv.IsList()); + ASSERT_FALSE(pv.IsMap()); + + ASSERT_EQ(pv.ValueBool(), false); + ASSERT_THROW(pv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueDouble(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueString(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueList(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueMap(), storage::PropertyValueException); + + const auto &cpv = pv; + + ASSERT_EQ(cpv.ValueBool(), false); + ASSERT_THROW(cpv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueDouble(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueString(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueList(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueMap(), storage::PropertyValueException); + + { + std::stringstream ss; + ss << pv.type(); + ASSERT_EQ(ss.str(), "bool"); + } + { + std::stringstream ss; + ss << pv; + ASSERT_EQ(ss.str(), "false"); + } + { + storage::PropertyValue pvtrue(true); + std::stringstream ss; + ss << pvtrue; + ASSERT_EQ(ss.str(), "true"); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, Int) { + storage::PropertyValue pv(123L); + + ASSERT_EQ(pv.type(), storage::PropertyValue::Type::Int); + + ASSERT_FALSE(pv.IsNull()); + ASSERT_FALSE(pv.IsBool()); + ASSERT_TRUE(pv.IsInt()); + ASSERT_FALSE(pv.IsDouble()); + ASSERT_FALSE(pv.IsString()); + ASSERT_FALSE(pv.IsList()); + ASSERT_FALSE(pv.IsMap()); + + ASSERT_THROW(pv.ValueBool(), storage::PropertyValueException); + ASSERT_EQ(pv.ValueInt(), 123L); + ASSERT_THROW(pv.ValueDouble(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueString(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueList(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueMap(), storage::PropertyValueException); + + const auto &cpv = pv; + + ASSERT_THROW(cpv.ValueBool(), storage::PropertyValueException); + ASSERT_EQ(cpv.ValueInt(), 123L); + ASSERT_THROW(cpv.ValueDouble(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueString(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueList(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueMap(), storage::PropertyValueException); + + { + std::stringstream ss; + ss << pv.type(); + ASSERT_EQ(ss.str(), "int"); + } + { + std::stringstream ss; + ss << pv; + ASSERT_EQ(ss.str(), "123"); + } + + { + storage::PropertyValue pvint(123); + ASSERT_EQ(pvint.type(), storage::PropertyValue::Type::Int); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, Double) { + storage::PropertyValue pv(123.5); + + ASSERT_EQ(pv.type(), storage::PropertyValue::Type::Double); + + ASSERT_FALSE(pv.IsNull()); + ASSERT_FALSE(pv.IsBool()); + ASSERT_FALSE(pv.IsInt()); + ASSERT_TRUE(pv.IsDouble()); + ASSERT_FALSE(pv.IsString()); + ASSERT_FALSE(pv.IsList()); + ASSERT_FALSE(pv.IsMap()); + + ASSERT_THROW(pv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueInt(), storage::PropertyValueException); + ASSERT_EQ(pv.ValueDouble(), 123.5); + ASSERT_THROW(pv.ValueString(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueList(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueMap(), storage::PropertyValueException); + + const auto &cpv = pv; + + ASSERT_THROW(cpv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueInt(), storage::PropertyValueException); + ASSERT_EQ(cpv.ValueDouble(), 123.5); + ASSERT_THROW(cpv.ValueString(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueList(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueMap(), storage::PropertyValueException); + + { + std::stringstream ss; + ss << pv.type(); + ASSERT_EQ(ss.str(), "double"); + } + { + std::stringstream ss; + ss << pv; + ASSERT_EQ(ss.str(), "123.5"); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, StringCopy) { + std::string str("nandare"); + storage::PropertyValue pv(str); + + ASSERT_EQ(str, "nandare"); + + ASSERT_EQ(pv.type(), storage::PropertyValue::Type::String); + + ASSERT_FALSE(pv.IsNull()); + ASSERT_FALSE(pv.IsBool()); + ASSERT_FALSE(pv.IsInt()); + ASSERT_FALSE(pv.IsDouble()); + ASSERT_TRUE(pv.IsString()); + ASSERT_FALSE(pv.IsList()); + ASSERT_FALSE(pv.IsMap()); + + ASSERT_THROW(pv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueDouble(), storage::PropertyValueException); + ASSERT_EQ(pv.ValueString(), "nandare"); + ASSERT_THROW(pv.ValueList(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueMap(), storage::PropertyValueException); + + const auto &cpv = pv; + + ASSERT_THROW(cpv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueDouble(), storage::PropertyValueException); + ASSERT_EQ(cpv.ValueString(), "nandare"); + ASSERT_THROW(cpv.ValueList(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueMap(), storage::PropertyValueException); + + { + std::stringstream ss; + ss << pv.type(); + ASSERT_EQ(ss.str(), "string"); + } + { + std::stringstream ss; + ss << pv; + ASSERT_EQ(ss.str(), "nandare"); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, StringMove) { + std::string str("nandare"); + storage::PropertyValue pv(std::move(str)); + + ASSERT_EQ(str, ""); + + ASSERT_EQ(pv.type(), storage::PropertyValue::Type::String); + + ASSERT_FALSE(pv.IsNull()); + ASSERT_FALSE(pv.IsBool()); + ASSERT_FALSE(pv.IsInt()); + ASSERT_FALSE(pv.IsDouble()); + ASSERT_TRUE(pv.IsString()); + ASSERT_FALSE(pv.IsList()); + ASSERT_FALSE(pv.IsMap()); + + ASSERT_THROW(pv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueDouble(), storage::PropertyValueException); + ASSERT_EQ(pv.ValueString(), "nandare"); + ASSERT_THROW(pv.ValueList(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueMap(), storage::PropertyValueException); + + const auto &cpv = pv; + + ASSERT_THROW(cpv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueDouble(), storage::PropertyValueException); + ASSERT_EQ(cpv.ValueString(), "nandare"); + ASSERT_THROW(cpv.ValueList(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueMap(), storage::PropertyValueException); + + { + std::stringstream ss; + ss << pv.type(); + ASSERT_EQ(ss.str(), "string"); + } + { + std::stringstream ss; + ss << pv; + ASSERT_EQ(ss.str(), "nandare"); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, ListCopy) { + std::vector<storage::PropertyValue> vec{storage::PropertyValue("nandare"), + storage::PropertyValue(123)}; + storage::PropertyValue pv(vec); + + ASSERT_EQ(vec.size(), 2); + ASSERT_EQ(vec[0].ValueString(), "nandare"); + ASSERT_EQ(vec[1].ValueInt(), 123); + + ASSERT_EQ(pv.type(), storage::PropertyValue::Type::List); + + ASSERT_FALSE(pv.IsNull()); + ASSERT_FALSE(pv.IsBool()); + ASSERT_FALSE(pv.IsInt()); + ASSERT_FALSE(pv.IsDouble()); + ASSERT_FALSE(pv.IsString()); + ASSERT_TRUE(pv.IsList()); + ASSERT_FALSE(pv.IsMap()); + + ASSERT_THROW(pv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueDouble(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueString(), storage::PropertyValueException); + { + const auto &ret = pv.ValueList(); + ASSERT_EQ(ret.size(), 2); + ASSERT_EQ(ret[0].ValueString(), "nandare"); + ASSERT_EQ(ret[1].ValueInt(), 123); + } + ASSERT_THROW(pv.ValueMap(), storage::PropertyValueException); + + const auto &cpv = pv; + + ASSERT_THROW(cpv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueDouble(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueString(), storage::PropertyValueException); + { + const auto &ret = cpv.ValueList(); + ASSERT_EQ(ret.size(), 2); + ASSERT_EQ(ret[0].ValueString(), "nandare"); + ASSERT_EQ(ret[1].ValueInt(), 123); + } + ASSERT_THROW(cpv.ValueMap(), storage::PropertyValueException); + + { + std::stringstream ss; + ss << pv.type(); + ASSERT_EQ(ss.str(), "list"); + } + { + std::stringstream ss; + ss << pv; + ASSERT_EQ(ss.str(), "[nandare, 123]"); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, ListMove) { + std::vector<storage::PropertyValue> vec{storage::PropertyValue("nandare"), + storage::PropertyValue(123)}; + storage::PropertyValue pv(std::move(vec)); + + ASSERT_EQ(vec.size(), 0); + + ASSERT_EQ(pv.type(), storage::PropertyValue::Type::List); + + ASSERT_FALSE(pv.IsNull()); + ASSERT_FALSE(pv.IsBool()); + ASSERT_FALSE(pv.IsInt()); + ASSERT_FALSE(pv.IsDouble()); + ASSERT_FALSE(pv.IsString()); + ASSERT_TRUE(pv.IsList()); + ASSERT_FALSE(pv.IsMap()); + + ASSERT_THROW(pv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueDouble(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueString(), storage::PropertyValueException); + { + const auto &ret = pv.ValueList(); + ASSERT_EQ(ret.size(), 2); + ASSERT_EQ(ret[0].ValueString(), "nandare"); + ASSERT_EQ(ret[1].ValueInt(), 123); + } + ASSERT_THROW(pv.ValueMap(), storage::PropertyValueException); + + const auto &cpv = pv; + + ASSERT_THROW(cpv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueDouble(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueString(), storage::PropertyValueException); + { + const auto &ret = cpv.ValueList(); + ASSERT_EQ(ret.size(), 2); + ASSERT_EQ(ret[0].ValueString(), "nandare"); + ASSERT_EQ(ret[1].ValueInt(), 123); + } + ASSERT_THROW(cpv.ValueMap(), storage::PropertyValueException); + + { + std::stringstream ss; + ss << pv.type(); + ASSERT_EQ(ss.str(), "list"); + } + { + std::stringstream ss; + ss << pv; + ASSERT_EQ(ss.str(), "[nandare, 123]"); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, MapCopy) { + std::map<std::string, storage::PropertyValue> map{ + {"nandare", storage::PropertyValue(123)}}; + storage::PropertyValue pv(map); + + ASSERT_EQ(map.size(), 1); + ASSERT_EQ(map.at("nandare").ValueInt(), 123); + + ASSERT_EQ(pv.type(), storage::PropertyValue::Type::Map); + + ASSERT_FALSE(pv.IsNull()); + ASSERT_FALSE(pv.IsBool()); + ASSERT_FALSE(pv.IsInt()); + ASSERT_FALSE(pv.IsDouble()); + ASSERT_FALSE(pv.IsString()); + ASSERT_FALSE(pv.IsList()); + ASSERT_TRUE(pv.IsMap()); + + ASSERT_THROW(pv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueDouble(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueString(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueList(), storage::PropertyValueException); + { + const auto &ret = pv.ValueMap(); + ASSERT_EQ(ret.size(), 1); + ASSERT_EQ(ret.at("nandare").ValueInt(), 123); + } + + const auto &cpv = pv; + + ASSERT_THROW(cpv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueDouble(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueString(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueList(), storage::PropertyValueException); + { + const auto &ret = cpv.ValueMap(); + ASSERT_EQ(ret.size(), 1); + ASSERT_EQ(ret.at("nandare").ValueInt(), 123); + } + + { + std::stringstream ss; + ss << pv.type(); + ASSERT_EQ(ss.str(), "map"); + } + { + std::stringstream ss; + ss << pv; + ASSERT_EQ(ss.str(), "{nandare: 123}"); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, MapMove) { + std::map<std::string, storage::PropertyValue> map{ + {"nandare", storage::PropertyValue(123)}}; + storage::PropertyValue pv(std::move(map)); + + ASSERT_EQ(map.size(), 0); + + ASSERT_EQ(pv.type(), storage::PropertyValue::Type::Map); + + ASSERT_FALSE(pv.IsNull()); + ASSERT_FALSE(pv.IsBool()); + ASSERT_FALSE(pv.IsInt()); + ASSERT_FALSE(pv.IsDouble()); + ASSERT_FALSE(pv.IsString()); + ASSERT_FALSE(pv.IsList()); + ASSERT_TRUE(pv.IsMap()); + + ASSERT_THROW(pv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueDouble(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueString(), storage::PropertyValueException); + ASSERT_THROW(pv.ValueList(), storage::PropertyValueException); + { + const auto &ret = pv.ValueMap(); + ASSERT_EQ(ret.size(), 1); + ASSERT_EQ(ret.at("nandare").ValueInt(), 123); + } + + const auto &cpv = pv; + + ASSERT_THROW(cpv.ValueBool(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueInt(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueDouble(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueString(), storage::PropertyValueException); + ASSERT_THROW(cpv.ValueList(), storage::PropertyValueException); + { + const auto &ret = cpv.ValueMap(); + ASSERT_EQ(ret.size(), 1); + ASSERT_EQ(ret.at("nandare").ValueInt(), 123); + } + + { + std::stringstream ss; + ss << pv.type(); + ASSERT_EQ(ss.str(), "map"); + } + { + std::stringstream ss; + ss << pv; + ASSERT_EQ(ss.str(), "{nandare: 123}"); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, CopyConstructor) { + std::vector<storage::PropertyValue> vec{storage::PropertyValue(true), + storage::PropertyValue(123)}; + std::map<std::string, storage::PropertyValue> map{ + {"nandare", storage::PropertyValue(false)}}; + std::vector<storage::PropertyValue> data{ + storage::PropertyValue(), storage::PropertyValue(true), + storage::PropertyValue(123), storage::PropertyValue(123.5), + storage::PropertyValue("nandare"), storage::PropertyValue(vec), + storage::PropertyValue(map)}; + for (const auto &item : data) { + storage::PropertyValue pv(item); + ASSERT_EQ(pv.type(), item.type()); + switch (item.type()) { + case storage::PropertyValue::Type::Null: + ASSERT_TRUE(pv.IsNull()); + break; + case storage::PropertyValue::Type::Bool: + ASSERT_EQ(pv.ValueBool(), item.ValueBool()); + break; + case storage::PropertyValue::Type::Int: + ASSERT_EQ(pv.ValueInt(), item.ValueInt()); + break; + case storage::PropertyValue::Type::Double: + ASSERT_EQ(pv.ValueDouble(), item.ValueDouble()); + break; + case storage::PropertyValue::Type::String: + ASSERT_EQ(pv.ValueString(), item.ValueString()); + break; + case storage::PropertyValue::Type::List: + ASSERT_EQ(pv.ValueList(), item.ValueList()); + break; + case storage::PropertyValue::Type::Map: + ASSERT_EQ(pv.ValueMap(), item.ValueMap()); + break; + } + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, MoveConstructor) { + std::vector<storage::PropertyValue> vec{storage::PropertyValue(true), + storage::PropertyValue(123)}; + std::map<std::string, storage::PropertyValue> map{ + {"nandare", storage::PropertyValue(false)}}; + std::vector<storage::PropertyValue> data{ + storage::PropertyValue(), storage::PropertyValue(true), + storage::PropertyValue(123), storage::PropertyValue(123.5), + storage::PropertyValue("nandare"), storage::PropertyValue(vec), + storage::PropertyValue(map)}; + for (auto &item : data) { + storage::PropertyValue copy(item); + storage::PropertyValue pv(std::move(item)); + ASSERT_EQ(item.type(), storage::PropertyValue::Type::Null); + ASSERT_EQ(pv.type(), copy.type()); + switch (copy.type()) { + case storage::PropertyValue::Type::Null: + ASSERT_TRUE(pv.IsNull()); + break; + case storage::PropertyValue::Type::Bool: + ASSERT_EQ(pv.ValueBool(), copy.ValueBool()); + break; + case storage::PropertyValue::Type::Int: + ASSERT_EQ(pv.ValueInt(), copy.ValueInt()); + break; + case storage::PropertyValue::Type::Double: + ASSERT_EQ(pv.ValueDouble(), copy.ValueDouble()); + break; + case storage::PropertyValue::Type::String: + ASSERT_EQ(pv.ValueString(), copy.ValueString()); + break; + case storage::PropertyValue::Type::List: + ASSERT_EQ(pv.ValueList(), copy.ValueList()); + break; + case storage::PropertyValue::Type::Map: + ASSERT_EQ(pv.ValueMap(), copy.ValueMap()); + break; + } + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, CopyAssignment) { + std::vector<storage::PropertyValue> vec{storage::PropertyValue(true), + storage::PropertyValue(123)}; + std::map<std::string, storage::PropertyValue> map{ + {"nandare", storage::PropertyValue(false)}}; + std::vector<storage::PropertyValue> data{ + storage::PropertyValue(), storage::PropertyValue(true), + storage::PropertyValue(123), storage::PropertyValue(123.5), + storage::PropertyValue("nandare"), storage::PropertyValue(vec), + storage::PropertyValue(map)}; + for (const auto &item : data) { + storage::PropertyValue pv(123); + pv = item; + ASSERT_EQ(pv.type(), item.type()); + switch (item.type()) { + case storage::PropertyValue::Type::Null: + ASSERT_TRUE(pv.IsNull()); + break; + case storage::PropertyValue::Type::Bool: + ASSERT_EQ(pv.ValueBool(), item.ValueBool()); + break; + case storage::PropertyValue::Type::Int: + ASSERT_EQ(pv.ValueInt(), item.ValueInt()); + break; + case storage::PropertyValue::Type::Double: + ASSERT_EQ(pv.ValueDouble(), item.ValueDouble()); + break; + case storage::PropertyValue::Type::String: + ASSERT_EQ(pv.ValueString(), item.ValueString()); + break; + case storage::PropertyValue::Type::List: + ASSERT_EQ(pv.ValueList(), item.ValueList()); + break; + case storage::PropertyValue::Type::Map: + ASSERT_EQ(pv.ValueMap(), item.ValueMap()); + break; + } + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, MoveAssignment) { + std::vector<storage::PropertyValue> vec{storage::PropertyValue(true), + storage::PropertyValue(123)}; + std::map<std::string, storage::PropertyValue> map{ + {"nandare", storage::PropertyValue(false)}}; + std::vector<storage::PropertyValue> data{ + storage::PropertyValue(), storage::PropertyValue(true), + storage::PropertyValue(123), storage::PropertyValue(123.5), + storage::PropertyValue("nandare"), storage::PropertyValue(vec), + storage::PropertyValue(map)}; + for (auto &item : data) { + storage::PropertyValue copy(item); + storage::PropertyValue pv(123); + pv = std::move(item); + ASSERT_EQ(item.type(), storage::PropertyValue::Type::Null); + ASSERT_EQ(pv.type(), copy.type()); + switch (copy.type()) { + case storage::PropertyValue::Type::Null: + ASSERT_TRUE(pv.IsNull()); + break; + case storage::PropertyValue::Type::Bool: + ASSERT_EQ(pv.ValueBool(), copy.ValueBool()); + break; + case storage::PropertyValue::Type::Int: + ASSERT_EQ(pv.ValueInt(), copy.ValueInt()); + break; + case storage::PropertyValue::Type::Double: + ASSERT_EQ(pv.ValueDouble(), copy.ValueDouble()); + break; + case storage::PropertyValue::Type::String: + ASSERT_EQ(pv.ValueString(), copy.ValueString()); + break; + case storage::PropertyValue::Type::List: + ASSERT_EQ(pv.ValueList(), copy.ValueList()); + break; + case storage::PropertyValue::Type::Map: + ASSERT_EQ(pv.ValueMap(), copy.ValueMap()); + break; + } + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, CopyAssignmentSelf) { + storage::PropertyValue pv("nandare"); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wself-assign-overloaded" + pv = pv; +#pragma clang diagnostic pop + ASSERT_EQ(pv.type(), storage::PropertyValue::Type::String); + ASSERT_EQ(pv.ValueString(), "nandare"); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, MoveAssignmentSelf) { + storage::PropertyValue pv("nandare"); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wself-move" + pv = std::move(pv); +#pragma clang diagnostic pop + ASSERT_EQ(pv.type(), storage::PropertyValue::Type::String); + ASSERT_EQ(pv.ValueString(), "nandare"); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, Equal) { + std::vector<storage::PropertyValue> vec{storage::PropertyValue(true), + storage::PropertyValue(123)}; + std::map<std::string, storage::PropertyValue> map{ + {"nandare", storage::PropertyValue(false)}}; + std::vector<storage::PropertyValue> data{ + storage::PropertyValue(), storage::PropertyValue(true), + storage::PropertyValue(123), storage::PropertyValue(123.5), + storage::PropertyValue("nandare"), storage::PropertyValue(vec), + storage::PropertyValue(map)}; + for (const auto item1 : data) { + for (const auto item2 : data) { + if (item1.type() == item2.type()) { + ASSERT_TRUE(item1 == item2); + } else { + ASSERT_FALSE(item1 == item2); + } + } + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(PropertyValue, Less) { + std::vector<storage::PropertyValue> vec{storage::PropertyValue(true), + storage::PropertyValue(123)}; + std::map<std::string, storage::PropertyValue> map{ + {"nandare", storage::PropertyValue(false)}}; + std::vector<storage::PropertyValue> data{ + storage::PropertyValue(), storage::PropertyValue(true), + storage::PropertyValue(123), storage::PropertyValue(123.5), + storage::PropertyValue("nandare"), storage::PropertyValue(vec), + storage::PropertyValue(map)}; + for (size_t i = 0; i < data.size(); ++i) { + for (size_t j = 0; j < data.size(); ++j) { + auto item1 = data[i]; + auto item2 = data[j]; + if (i < j) { + ASSERT_TRUE(item1 < item2); + } else { + ASSERT_FALSE(item1 < item2); + } + } + } +}