storage/model - added typed_value system and tests. Modified utils slightly (backward compatible).
Summary: Added TypedValue system. Test Plan: ??? Reviewers: sale, buda Reviewed By: buda Subscribers: pullbot, florijan, buda, sale Differential Revision: https://phabricator.memgraph.io/D46
This commit is contained in:
parent
99b8a4f234
commit
10c7514c8a
@ -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
|
||||
|
117
include/storage/model/typed_value.hpp
Normal file
117
include/storage/model/typed_value.hpp
Normal file
@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#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<TypedValue, TypedValue, TypedValue> {
|
||||
|
||||
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<std::string>(new std::string(value));
|
||||
}
|
||||
TypedValue(const char* value) : type_(Type::String) {
|
||||
new (&string_v) std::shared_ptr<std::string>(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 <typename T> 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<std::string> 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);
|
82
include/storage/model/typed_value_store.hpp
Normal file
82
include/storage/model/typed_value_store.hpp
Normal file
@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#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<TypedValueStore>;
|
||||
|
||||
/** 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<typename TValue>
|
||||
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<void(const TKey key, const TypedValue& prop)> handler,
|
||||
std::function<void()> finish = {}) const;
|
||||
|
||||
private:
|
||||
std::vector<std::pair<TKey, TypedValue>> props_;
|
||||
};
|
57
include/storage/model/typed_value_utils.h
Normal file
57
include/storage/model/typed_value_utils.h
Normal file
@ -0,0 +1,57 @@
|
||||
//
|
||||
// Copyright 2017 Memgraph
|
||||
// Created by Florijan Stamenkovic on 01.02.17.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
#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<bool>() ? "true" : "false");
|
||||
break;
|
||||
case TypedValue::Type::String:
|
||||
write_key(key) << '"' << value.Value<std::string>() << '"';
|
||||
break;
|
||||
case TypedValue::Type::Int:
|
||||
write_key(key) << value.Value<int>();
|
||||
break;
|
||||
case TypedValue::Type::Float:
|
||||
write_key(key) << value.Value<float>();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
auto finish = [&ostream]() { ostream << '}' << std::endl; };
|
||||
|
||||
store.Accept(handler, finish);
|
||||
}
|
@ -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
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "utils/auto_scope.hpp"
|
||||
@ -21,6 +22,12 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
BasicException(const char* format, Args &&... args) noexcept
|
||||
: BasicException(fmt::format(std::string(format), std::forward<Args>(args)...))
|
||||
{
|
||||
}
|
||||
|
||||
const char *what() const noexcept override { return message_.c_str(); }
|
||||
|
||||
private:
|
||||
|
@ -1,25 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
template <class Derived, class Other = Derived>
|
||||
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<typename TLhs, typename TRhs=TLhs, typename TReturn=bool>
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
363
src/storage/model/typed_value.cpp
Normal file
363
src/storage/model/typed_value.cpp
Normal file
@ -0,0 +1,363 @@
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "storage/model/typed_value.hpp"
|
||||
#include "utils/assert.hpp"
|
||||
|
||||
// Value extraction template instantiations
|
||||
template<>
|
||||
bool TypedValue::Value<bool>() const {
|
||||
runtime_assert(type_ == TypedValue::Type::Bool, "Incompatible template param and type");
|
||||
return bool_v;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string TypedValue::Value<std::string>() const {
|
||||
runtime_assert(type_ == TypedValue::Type::String, "Incompatible template param and type");
|
||||
return *string_v;
|
||||
}
|
||||
|
||||
template<>
|
||||
int TypedValue::Value<int>() const {
|
||||
runtime_assert(type_ == TypedValue::Type::Int, "Incompatible template param and type");
|
||||
return int_v;
|
||||
}
|
||||
|
||||
template<>
|
||||
float TypedValue::Value<float>() 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<std::string>(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<bool>() ? "true" : "false");
|
||||
case TypedValue::Type::String:
|
||||
return os << property.Value<std::string>();
|
||||
case TypedValue::Type::Int:
|
||||
return os << property.Value<int>();
|
||||
case TypedValue::Type::Float:
|
||||
return os << property.Value<float>();
|
||||
}
|
||||
permanent_fail("Unsupported TypedValue::Type");
|
||||
}
|
||||
|
||||
TypedValue &TypedValue::operator=(TypedValue &&other) {
|
||||
|
||||
// set the type of this
|
||||
const_cast<Type&>(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<std::string>();
|
||||
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<int>();
|
||||
case TypedValue::Type::Float:
|
||||
return prop.Value<float>();
|
||||
|
||||
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<std::string>() < b.Value<std::string>();
|
||||
}
|
||||
|
||||
// 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<int>() < b.Value<int>();
|
||||
}
|
||||
|
||||
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<std::string>() == b.Value<std::string>();
|
||||
}
|
||||
|
||||
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<bool>() == b.Value<bool>();
|
||||
}
|
||||
// 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<int>() == b.Value<int>();
|
||||
|
||||
}
|
||||
|
||||
TypedValue operator!(const TypedValue& a) {
|
||||
switch (a.type_) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Bool:
|
||||
return TypedValue(!a.Value<bool>());
|
||||
|
||||
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<std::string>();
|
||||
case TypedValue::Type::Int:
|
||||
return std::to_string(prop.Value<int>());
|
||||
case TypedValue::Type::Float:
|
||||
return fmt::format("{}", prop.Value<float>());
|
||||
|
||||
// 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<int>();
|
||||
case TypedValue::Type::Float:
|
||||
return -a.Value<float>();
|
||||
|
||||
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<int>() + b.Value<int>();
|
||||
}
|
||||
|
||||
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<int>() - b.Value<int>();
|
||||
}
|
||||
|
||||
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<int>() / b.Value<int>();
|
||||
}
|
||||
|
||||
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<int>() * b.Value<int>();
|
||||
}
|
||||
|
||||
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<int>() % b.Value<int>();
|
||||
}
|
||||
|
||||
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<bool>() && b.Value<bool>();
|
||||
} 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<bool>() || b.Value<bool>();
|
||||
} else
|
||||
throw TypedValueException("Invalid logical and operand types({} && {})", a.type_, b.type_);
|
||||
}
|
68
src/storage/model/typed_value_store.cpp
Normal file
68
src/storage/model/typed_value_store.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
#include <algorithm>
|
||||
|
||||
#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<typename TValue>
|
||||
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<std::string>(const TKey &key, const std::string &value);
|
||||
template void TypedValueStore::set<bool>(const TKey &key, const bool &value);
|
||||
template void TypedValueStore::set<int>(const TKey &key, const int &value);
|
||||
template void TypedValueStore::set<float>(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<TKey, TypedValue> &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<void(const TypedValueStore::TKey, const TypedValue &)> handler,
|
||||
std::function<void()> finish) const {
|
||||
if (handler)
|
||||
for (const auto& prop : props_)
|
||||
handler(prop.first, prop.second);
|
||||
|
||||
if (finish)
|
||||
finish();
|
||||
|
||||
}
|
||||
|
||||
|
293
tests/unit/typed_value.cpp
Normal file
293
tests/unit/typed_value.cpp
Normal file
@ -0,0 +1,293 @@
|
||||
//
|
||||
// Copyright 2017 Memgraph
|
||||
// Created by Florijan Stamenkovic on 24.01.17..
|
||||
//
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#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<bool>());
|
||||
}
|
||||
|
||||
void EXPECT_PROP_TRUE(const TypedValue& a) {
|
||||
EXPECT_TRUE(a.type_ == TypedValue::Type::Bool && a.Value<bool>());
|
||||
}
|
||||
|
||||
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<bool>(), true);
|
||||
EXPECT_EQ(TypedValue(false).Value<bool>(), false);
|
||||
|
||||
EXPECT_EQ(TypedValue(std::string("bla")).Value<std::string>(), "bla");
|
||||
EXPECT_EQ(TypedValue("bla2").Value<std::string>(), "bla2");
|
||||
|
||||
EXPECT_EQ(TypedValue(55).Value<int>(), 55);
|
||||
|
||||
EXPECT_FLOAT_EQ(TypedValue(66.6f).Value<float>(), 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<int>()), -2);
|
||||
EXPECT_FLOAT_EQ((-TypedValue(2.0f).Value<float>()), -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<TypedValue(const TypedValue&, const TypedValue&)> 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<int>(), 5);
|
||||
EXPECT_FLOAT_EQ((TypedValue(2.5f) + TypedValue(1.25f)).Value<float>(), 3.75);
|
||||
EXPECT_EQ((TypedValue("one") + TypedValue("two")).Value<std::string>(), "onetwo");
|
||||
|
||||
// sum of string and numbers
|
||||
EXPECT_EQ((TypedValue("one") + TypedValue(1)).Value<std::string>(), "one1");
|
||||
EXPECT_EQ((TypedValue(1) + TypedValue("one")).Value<std::string>(), "1one");
|
||||
EXPECT_EQ((TypedValue("one") + TypedValue(3.2f)).Value<std::string>(), "one3.2");
|
||||
EXPECT_EQ((TypedValue(3.2f) + TypedValue("one")).Value<std::string>(), "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<int>(), -1);
|
||||
EXPECT_FLOAT_EQ((TypedValue(2.5f) - TypedValue(2.25f)).Value<float>(), 0.25);
|
||||
|
||||
// implicit casting
|
||||
EXPECT_FLOAT_EQ((TypedValue(2) - TypedValue(0.5f)).Value<float>(), 1.5f);
|
||||
EXPECT_FLOAT_EQ((TypedValue(2.5f) - TypedValue(2)).Value<float>(), 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<float>(), 2.5f);
|
||||
|
||||
EXPECT_FLOAT_EQ((TypedValue(10) / TypedValue(4.0f)).Value<float>(), 2.5f);
|
||||
EXPECT_FLOAT_EQ((TypedValue(10.0f) / TypedValue(4)).Value<float>(), 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<float>(), 12.5f * 6.6f);
|
||||
EXPECT_FLOAT_EQ((TypedValue(10) * TypedValue(4.5f)).Value<float>(), 10 * 4.5f);
|
||||
EXPECT_FLOAT_EQ((TypedValue(10.2f) * TypedValue(4)).Value<float>(), 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<float>(), 0.25f);
|
||||
|
||||
EXPECT_FLOAT_EQ((TypedValue(10) % TypedValue(4.0f)).Value<float>(), 2.0f);
|
||||
EXPECT_FLOAT_EQ((TypedValue(10.0f) % TypedValue(4)).Value<float>(), 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<TypedValue(const TypedValue&, const TypedValue&)> 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));
|
||||
}
|
117
tests/unit/typed_value_store.cpp
Normal file
117
tests/unit/typed_value_store.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
//
|
||||
// Copyright 2017 Memgraph
|
||||
// Created by Florijan Stamenkovic on 24.01.17..
|
||||
//
|
||||
#include <vector>
|
||||
|
||||
#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<string>(), some_string);
|
||||
props.set(120, 42);
|
||||
EXPECT_EQ(TypedValue(props.at(120)).Value<int>(), 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<int>(), 42);
|
||||
props.set(10, 0.25f);
|
||||
EXPECT_EQ(props.at(10).type_, TypedValue::Type::Float);
|
||||
EXPECT_FLOAT_EQ(props.at(10).Value<float>(), 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user