Add MemoryResource to TypedValue

Summary:
This diff only introduces a MemoryResource member to TypedValue and correctly
propagates through various constructors and assignments. At the moment,
MemoryResource is not used to actually allocate anything.

Reviewers: mtomic, llugovic, mferencevic, msantl

Reviewed By: mtomic

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2085
This commit is contained in:
Teon Banek 2019-05-23 12:43:52 +02:00
parent 1bcb3cbd86
commit 0152ac2dfe
4 changed files with 519 additions and 130 deletions

View File

@ -47,6 +47,7 @@ You need to watch out for the following.
* Is the move assignment done correctly, also it may throw an exception.
* Is the copy construction done with the right allocator.
* Is the copy assignment done correctly.
* Using `auto` makes allocator propagation rules rather ambiguous.
## Classes & Object Oriented Programming

View File

@ -14,7 +14,13 @@
namespace query {
TypedValue::TypedValue(const PropertyValue &value) {
TypedValue::TypedValue(const PropertyValue &value)
// TODO: MemoryResource in PropertyValue
: TypedValue(value, utils::NewDeleteResource()) {}
TypedValue::TypedValue(const PropertyValue &value,
utils::MemoryResource *memory)
: memory_(memory) {
switch (value.type()) {
case PropertyValue::Type::Null:
type_ = Type::Null;
@ -51,30 +57,35 @@ TypedValue::TypedValue(const PropertyValue &value) {
LOG(FATAL) << "Unsupported type";
}
TypedValue::TypedValue(PropertyValue &&value) noexcept {
switch (value.type()) {
TypedValue::TypedValue(PropertyValue &&other) /* noexcept */
// TODO: MemoryResource in PropertyValue, so this can be noexcept
: TypedValue(std::move(other), utils::NewDeleteResource()) {}
TypedValue::TypedValue(PropertyValue &&other, utils::MemoryResource *memory)
: memory_(memory) {
switch (other.type()) {
case PropertyValue::Type::Null:
type_ = Type::Null;
break;
case PropertyValue::Type::Bool:
type_ = Type::Bool;
bool_v = value.Value<bool>();
bool_v = other.Value<bool>();
break;
case PropertyValue::Type::Int:
type_ = Type::Int;
int_v = value.Value<int64_t>();
int_v = other.Value<int64_t>();
break;
case PropertyValue::Type::Double:
type_ = Type::Double;
double_v = value.Value<double>();
double_v = other.Value<double>();
break;
case PropertyValue::Type::String:
type_ = Type::String;
new (&string_v) std::string(std::move(value.Value<std::string>()));
new (&string_v) std::string(std::move(other.Value<std::string>()));
break;
case PropertyValue::Type::List: {
type_ = Type::List;
auto &vec = value.Value<std::vector<PropertyValue>>();
auto &vec = other.Value<std::vector<PropertyValue>>();
new (&list_v)
std::vector<TypedValue>(std::make_move_iterator(vec.begin()),
std::make_move_iterator(vec.end()));
@ -82,7 +93,7 @@ TypedValue::TypedValue(PropertyValue &&value) noexcept {
}
case PropertyValue::Type::Map: {
type_ = Type::Map;
auto &map = value.Value<std::map<std::string, PropertyValue>>();
auto &map = other.Value<std::map<std::string, PropertyValue>>();
new (&map_v) std::map<std::string, TypedValue>(
std::make_move_iterator(map.begin()),
std::make_move_iterator(map.end()));
@ -90,10 +101,16 @@ TypedValue::TypedValue(PropertyValue &&value) noexcept {
}
}
value = PropertyValue::Null;
other = PropertyValue::Null;
}
TypedValue::TypedValue(const TypedValue &other) : type_(other.type_) {
TypedValue::TypedValue(const TypedValue &other)
: TypedValue(other, std::allocator_traits<utils::Allocator<TypedValue>>::
select_on_container_copy_construction(other.memory_)
.GetMemoryResource()) {}
TypedValue::TypedValue(const TypedValue &other, utils::MemoryResource *memory)
: memory_(memory), type_(other.type_) {
switch (other.type_) {
case TypedValue::Type::Null:
return;
@ -128,7 +145,11 @@ TypedValue::TypedValue(const TypedValue &other) : type_(other.type_) {
LOG(FATAL) << "Unsupported TypedValue::Type";
}
TypedValue::TypedValue(TypedValue &&other) noexcept : type_(other.type_) {
TypedValue::TypedValue(TypedValue &&other) noexcept
: TypedValue(std::move(other), other.memory_) {}
TypedValue::TypedValue(TypedValue &&other, utils::MemoryResource *memory)
: memory_(memory), type_(other.type_) {
switch (other.type_) {
case TypedValue::Type::Null:
break;
@ -247,7 +268,7 @@ bool TypedValue::IsPropertyValue() const {
}
}
std::ostream &operator<<(std::ostream &os, const TypedValue::Type type) {
std::ostream &operator<<(std::ostream &os, const TypedValue::Type &type) {
switch (type) {
case TypedValue::Type::Null:
return os << "null";
@ -312,7 +333,7 @@ std::ostream &operator<<(std::ostream &os, const TypedValue &value) {
if (this->type_ == TypedValue::Type::typed_value_type) { \
this->member = other; \
} else { \
*this = TypedValue(other); \
*this = TypedValue(other, memory_); \
} \
\
return *this; \
@ -339,7 +360,7 @@ DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const Path &, Path, path_v)
if (this->type_ == TypedValue::Type::typed_value_type) { \
this->member = std::move(other); \
} else { \
*this = TypedValue(std::move(other)); \
*this = TypedValue(std::move(other), memory_); \
} \
\
return *this; \
@ -356,6 +377,14 @@ DEFINE_TYPED_VALUE_MOVE_ASSIGNMENT(Path, Path, path_v)
TypedValue &TypedValue::operator=(const TypedValue &other) {
if (this != &other) {
// NOTE: STL uses
// std::allocator_traits<>::propagate_on_container_copy_assignment to
// determine whether to take the allocator from `other`, or use the one in
// `this`. Our utils::Allocator never propagates, so we use the allocator
// from `this`.
static_assert(!std::allocator_traits<utils::Allocator<TypedValue>>::
propagate_on_container_copy_assignment::value,
"Allocator propagation not implemented");
DestroyValue();
type_ = other.type_;
@ -398,6 +427,14 @@ TypedValue &TypedValue::operator=(const TypedValue &other) {
TypedValue &TypedValue::operator=(TypedValue &&other) noexcept {
if (this != &other) {
DestroyValue();
// NOTE: STL uses
// std::allocator_traits<>::propagate_on_container_move_assignment to
// determine whether to take the allocator from `other`, or use the one in
// `this`. Our utils::Allocator never propagates, so we use the allocator
// from `this`.
static_assert(!std::allocator_traits<utils::Allocator<TypedValue>>::
propagate_on_container_move_assignment::value,
"Allocator propagation not implemented");
type_ = other.type_;
switch (other.type_) {
@ -518,55 +555,56 @@ TypedValue operator<(const TypedValue &a, const TypedValue &b) {
throw TypedValueException("Invalid 'less' operand types({} + {})", a.type(),
b.type());
if (a.IsNull() || b.IsNull()) return TypedValue::Null;
if (a.IsNull() || b.IsNull()) return TypedValue(a.GetMemoryResource());
if (a.IsString() || b.IsString()) {
if (a.type() != b.type()) {
throw TypedValueException("Invalid 'less' operand types({} + {})",
a.type(), b.type());
} else {
return a.Value<std::string>() < b.Value<std::string>();
return TypedValue(a.Value<std::string>() < b.Value<std::string>(),
a.GetMemoryResource());
}
}
// at this point we only have int and double
if (a.IsDouble() || b.IsDouble()) {
return ToDouble(a) < ToDouble(b);
return TypedValue(ToDouble(a) < ToDouble(b), a.GetMemoryResource());
} else {
return a.Value<int64_t>() < b.Value<int64_t>();
return TypedValue(a.Value<int64_t>() < b.Value<int64_t>(),
a.GetMemoryResource());
}
}
/** Equality between two typed values that returns either a bool
* or Null TypedValue (never raises an exception).
*
* For the old version of equality that raised an exception
* when comparing incompatible types, see the version of
* this file at 2017-04-12.
*/
TypedValue operator==(const TypedValue &a, const TypedValue &b) {
if (a.IsNull() || b.IsNull()) return TypedValue::Null;
if (a.IsNull() || b.IsNull()) return TypedValue(a.GetMemoryResource());
// check we have values that can be compared
// this means that either they're the same type, or (int, double) combo
if ((a.type() != b.type() && !(a.IsNumeric() && b.IsNumeric()))) return false;
if ((a.type() != b.type() && !(a.IsNumeric() && b.IsNumeric())))
return TypedValue(false, a.GetMemoryResource());
switch (a.type()) {
case TypedValue::Type::Bool:
return a.Value<bool>() == b.Value<bool>();
return TypedValue(a.Value<bool>() == b.Value<bool>(),
a.GetMemoryResource());
case TypedValue::Type::Int:
if (b.IsDouble())
return ToDouble(a) == ToDouble(b);
return TypedValue(ToDouble(a) == ToDouble(b), a.GetMemoryResource());
else
return a.Value<int64_t>() == b.Value<int64_t>();
return TypedValue(a.Value<int64_t>() == b.Value<int64_t>(),
a.GetMemoryResource());
case TypedValue::Type::Double:
return ToDouble(a) == ToDouble(b);
return TypedValue(ToDouble(a) == ToDouble(b), a.GetMemoryResource());
case TypedValue::Type::String:
return a.Value<std::string>() == b.Value<std::string>();
return TypedValue(a.Value<std::string>() == b.Value<std::string>(),
a.GetMemoryResource());
case TypedValue::Type::Vertex:
return a.Value<VertexAccessor>() == b.Value<VertexAccessor>();
return TypedValue(a.Value<VertexAccessor>() == b.Value<VertexAccessor>(),
a.GetMemoryResource());
case TypedValue::Type::Edge:
return a.Value<EdgeAccessor>() == b.Value<EdgeAccessor>();
return TypedValue(a.Value<EdgeAccessor>() == b.Value<EdgeAccessor>(),
a.GetMemoryResource());
case TypedValue::Type::List: {
// We are not compatible with neo4j at this point. In neo4j 2 = [2]
// compares
@ -576,39 +614,44 @@ TypedValue operator==(const TypedValue &a, const TypedValue &b) {
// well. Because, why not?
// At memgraph we prefer sanity so [1,2] = [1,2] compares to true and
// 2 = [2] compares to false.
auto &list_a = a.ValueList();
auto &list_b = b.ValueList();
if (list_a.size() != list_b.size()) return false;
const auto &list_a = a.ValueList();
const auto &list_b = b.ValueList();
if (list_a.size() != list_b.size())
return TypedValue(false, a.GetMemoryResource());
// two arrays are considered equal (by neo) if all their
// elements are bool-equal. this means that:
// [1] == [null] -> false
// [null] == [null] -> true
// in that sense array-comparison never results in Null
return std::equal(list_a.begin(), list_a.end(), list_b.begin(),
TypedValue::BoolEqual{});
return TypedValue(std::equal(list_a.begin(), list_a.end(), list_b.begin(),
TypedValue::BoolEqual{}),
a.GetMemoryResource());
}
case TypedValue::Type::Map: {
auto &map_a = a.Value<std::map<std::string, TypedValue>>();
auto &map_b = b.Value<std::map<std::string, TypedValue>>();
if (map_a.size() != map_b.size()) return false;
for (auto &kv_a : map_a) {
const auto &map_a = a.Value<std::map<std::string, TypedValue>>();
const auto &map_b = b.Value<std::map<std::string, TypedValue>>();
if (map_a.size() != map_b.size())
return TypedValue(false, a.GetMemoryResource());
for (const auto &kv_a : map_a) {
auto found_b_it = map_b.find(kv_a.first);
if (found_b_it == map_b.end()) return false;
if (found_b_it == map_b.end())
return TypedValue(false, a.GetMemoryResource());
TypedValue comparison = kv_a.second == found_b_it->second;
if (comparison.IsNull() || !comparison.Value<bool>()) return false;
if (comparison.IsNull() || !comparison.Value<bool>())
return TypedValue(false, a.GetMemoryResource());
}
return true;
return TypedValue(true, a.GetMemoryResource());
}
case TypedValue::Type::Path:
return a.ValuePath() == b.ValuePath();
return TypedValue(a.ValuePath() == b.ValuePath(), a.GetMemoryResource());
default:
LOG(FATAL) << "Unhandled comparison for types";
}
}
TypedValue operator!(const TypedValue &a) {
if (a.IsNull()) return TypedValue::Null;
if (a.IsBool()) return TypedValue(!a.Value<bool>());
if (a.IsNull()) return TypedValue(a.GetMemoryResource());
if (a.IsBool()) return TypedValue(!a.Value<bool>(), a.GetMemoryResource());
throw TypedValueException("Invalid logical not operand type (!{})", a.type());
}
@ -628,16 +671,18 @@ std::string ValueToString(const TypedValue &value) {
}
TypedValue operator-(const TypedValue &a) {
if (a.IsNull()) return TypedValue::Null;
if (a.IsInt()) return -a.Value<int64_t>();
if (a.IsDouble()) return -a.Value<double>();
if (a.IsNull()) return TypedValue(a.GetMemoryResource());
if (a.IsInt()) return TypedValue(-a.Value<int64_t>(), a.GetMemoryResource());
if (a.IsDouble())
return TypedValue(-a.Value<double>(), a.GetMemoryResource());
throw TypedValueException("Invalid unary minus operand type (-{})", a.type());
}
TypedValue operator+(const TypedValue &a) {
if (a.IsNull()) return TypedValue::Null;
if (a.IsInt()) return +a.Value<int64_t>();
if (a.IsDouble()) return +a.Value<double>();
if (a.IsNull()) return TypedValue(a.GetMemoryResource());
if (a.IsInt()) return TypedValue(+a.Value<int64_t>(), a.GetMemoryResource());
if (a.IsDouble())
return TypedValue(+a.Value<double>(), a.GetMemoryResource());
throw TypedValueException("Invalid unary plus operand type (+{})", a.type());
}
@ -669,7 +714,7 @@ inline void EnsureArithmeticallyOk(const TypedValue &a, const TypedValue &b,
}
TypedValue operator+(const TypedValue &a, const TypedValue &b) {
if (a.IsNull() || b.IsNull()) return TypedValue::Null;
if (a.IsNull() || b.IsNull()) return TypedValue(a.GetMemoryResource());
if (a.IsList() || b.IsList()) {
std::vector<TypedValue> list;
@ -683,70 +728,78 @@ TypedValue operator+(const TypedValue &a, const TypedValue &b) {
};
append_list(a);
append_list(b);
return TypedValue(list);
return TypedValue(list, a.GetMemoryResource());
}
EnsureArithmeticallyOk(a, b, true, "addition");
// no more Bool nor Null, summing works on anything from here onward
if (a.IsString() || b.IsString()) return ValueToString(a) + ValueToString(b);
if (a.IsString() || b.IsString())
return TypedValue(ValueToString(a) + ValueToString(b),
a.GetMemoryResource());
// at this point we only have int and double
if (a.IsDouble() || b.IsDouble()) {
return ToDouble(a) + ToDouble(b);
return TypedValue(ToDouble(a) + ToDouble(b), a.GetMemoryResource());
} else {
return a.Value<int64_t>() + b.Value<int64_t>();
return TypedValue(a.Value<int64_t>() + b.Value<int64_t>(),
a.GetMemoryResource());
}
}
TypedValue operator-(const TypedValue &a, const TypedValue &b) {
if (a.IsNull() || b.IsNull()) return TypedValue::Null;
if (a.IsNull() || b.IsNull()) return TypedValue(a.GetMemoryResource());
EnsureArithmeticallyOk(a, b, false, "subtraction");
// at this point we only have int and double
if (a.IsDouble() || b.IsDouble()) {
return ToDouble(a) - ToDouble(b);
return TypedValue(ToDouble(a) - ToDouble(b), a.GetMemoryResource());
} else {
return a.Value<int64_t>() - b.Value<int64_t>();
return TypedValue(a.Value<int64_t>() - b.Value<int64_t>(),
a.GetMemoryResource());
}
}
TypedValue operator/(const TypedValue &a, const TypedValue &b) {
if (a.IsNull() || b.IsNull()) return TypedValue::Null;
if (a.IsNull() || b.IsNull()) return TypedValue(a.GetMemoryResource());
EnsureArithmeticallyOk(a, b, false, "division");
// at this point we only have int and double
if (a.IsDouble() || b.IsDouble()) {
return ToDouble(a) / ToDouble(b);
return TypedValue(ToDouble(a) / ToDouble(b), a.GetMemoryResource());
} else {
if (b.Value<int64_t>() == 0LL)
throw TypedValueException("Division by zero");
return a.Value<int64_t>() / b.Value<int64_t>();
return TypedValue(a.Value<int64_t>() / b.Value<int64_t>(),
a.GetMemoryResource());
}
}
TypedValue operator*(const TypedValue &a, const TypedValue &b) {
if (a.IsNull() || b.IsNull()) return TypedValue::Null;
if (a.IsNull() || b.IsNull()) return TypedValue(a.GetMemoryResource());
EnsureArithmeticallyOk(a, b, false, "multiplication");
// at this point we only have int and double
if (a.IsDouble() || b.IsDouble()) {
return ToDouble(a) * ToDouble(b);
return TypedValue(ToDouble(a) * ToDouble(b), a.GetMemoryResource());
} else {
return a.Value<int64_t>() * b.Value<int64_t>();
return TypedValue(a.Value<int64_t>() * b.Value<int64_t>(),
a.GetMemoryResource());
}
}
TypedValue operator%(const TypedValue &a, const TypedValue &b) {
if (a.IsNull() || b.IsNull()) return TypedValue::Null;
if (a.IsNull() || b.IsNull()) return TypedValue(a.GetMemoryResource());
EnsureArithmeticallyOk(a, b, false, "modulo");
// at this point we only have int and double
if (a.IsDouble() || b.IsDouble()) {
return (double)fmod(ToDouble(a), ToDouble(b));
return TypedValue(static_cast<double>(fmod(ToDouble(a), ToDouble(b))),
a.GetMemoryResource());
} else {
if (b.Value<int64_t>() == 0LL) throw TypedValueException("Mod with zero");
return a.Value<int64_t>() % b.Value<int64_t>();
return TypedValue(a.Value<int64_t>() % b.Value<int64_t>(),
a.GetMemoryResource());
}
}
@ -761,31 +814,36 @@ TypedValue operator&&(const TypedValue &a, const TypedValue &b) {
EnsureLogicallyOk(a, b, "logical AND");
// at this point we only have null and bool
// if either operand is false, the result is false
if (a.IsBool() && !a.Value<bool>()) return false;
if (b.IsBool() && !b.Value<bool>()) return false;
if (a.IsNull() || b.IsNull()) return TypedValue::Null;
if (a.IsBool() && !a.Value<bool>())
return TypedValue(false, a.GetMemoryResource());
if (b.IsBool() && !b.Value<bool>())
return TypedValue(false, a.GetMemoryResource());
if (a.IsNull() || b.IsNull()) return TypedValue(a.GetMemoryResource());
// neither is false, neither is null, thus both are true
return true;
return TypedValue(true, a.GetMemoryResource());
}
TypedValue operator||(const TypedValue &a, const TypedValue &b) {
EnsureLogicallyOk(a, b, "logical OR");
// at this point we only have null and bool
// if either operand is true, the result is true
if (a.IsBool() && a.Value<bool>()) return true;
if (b.IsBool() && b.Value<bool>()) return true;
if (a.IsNull() || b.IsNull()) return TypedValue::Null;
if (a.IsBool() && a.Value<bool>())
return TypedValue(true, a.GetMemoryResource());
if (b.IsBool() && b.Value<bool>())
return TypedValue(true, a.GetMemoryResource());
if (a.IsNull() || b.IsNull()) return TypedValue(a.GetMemoryResource());
// neither is true, neither is null, thus both are false
return false;
return TypedValue(false, a.GetMemoryResource());
}
TypedValue operator^(const TypedValue &a, const TypedValue &b) {
EnsureLogicallyOk(a, b, "logical XOR");
// at this point we only have null and bool
if (a.IsNull() || b.IsNull())
return TypedValue::Null;
return TypedValue(a.GetMemoryResource());
else
return static_cast<bool>(a.Value<bool>() ^ b.Value<bool>());
return TypedValue(static_cast<bool>(a.Value<bool>() ^ b.Value<bool>()),
a.GetMemoryResource());
}
bool TypedValue::BoolEqual::operator()(const TypedValue &lhs,

View File

@ -14,6 +14,7 @@
#include "storage/edge_accessor.hpp"
#include "storage/vertex_accessor.hpp"
#include "utils/exceptions.hpp"
#include "utils/memory.hpp"
namespace query {
@ -48,18 +49,6 @@ class TypedValue {
size_t operator()(const TypedValue &value) const;
};
/**
* Unordered set of TypedValue items. Can contain at most one Null element,
* and treats an integral and floating point value as same if they are equal
* in the floating point domain (TypedValue operator== behaves the same).
* */
using unordered_set = std::unordered_set<TypedValue, Hash, BoolEqual>;
using value_map_t = std::map<std::string, TypedValue>;
/** 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,
@ -74,65 +63,204 @@ class TypedValue {
Path
};
/**
* Unordered set of TypedValue items. Can contain at most one Null element,
* and treats an integral and floating point value as same if they are equal
* in the floating point domain (TypedValue operator== behaves the same).
*/
using unordered_set = std::unordered_set<TypedValue, Hash, BoolEqual>;
using value_map_t = std::map<std::string, TypedValue>;
/** Allocator type so that STL containers are aware that we need one */
using allocator_type = utils::Allocator<TypedValue>;
/** Construct a Null value with default utils::NewDeleteResource(). */
TypedValue() : type_(Type::Null) {}
/** Construct a Null value with given utils::MemoryResource. */
explicit TypedValue(utils::MemoryResource *memory)
: memory_(memory), type_(Type::Null) {}
// single static reference to Null, used whenever Null should be returned
// TODO: Remove this as it may be needed to construct TypedValue with a
// different MemoryResource.
static const TypedValue Null;
/**
* Construct a copy of other.
* utils::MemoryResource is obtained by calling
* std::allocator_traits<>::select_on_container_copy_construction(other.memory_).
* Since we use utils::Allocator, which does not propagate, this means that
* memory_ will be the default utils::NewDeleteResource().
*/
TypedValue(const TypedValue &other);
/** Construct a copy using the given utils::MemoryResource */
TypedValue(const TypedValue &other, utils::MemoryResource *memory);
/**
* Construct with the value of other.
* utils::MemoryResource is obtained from other. After the move, other will be
* set to Null.
*/
TypedValue(TypedValue &&other) noexcept;
/**
* Construct with the value of other, but use the given utils::MemoryResource.
* After the move, other will be set to Null.
* If `*memory != *other.GetMemoryResource()`, then a copy is made instead of
* a move.
*/
TypedValue(TypedValue &&other, utils::MemoryResource *memory);
// constructors for primitive types
TypedValue(bool value) : type_(Type::Bool) { bool_v = value; }
TypedValue(int value) : type_(Type::Int) { int_v = value; }
TypedValue(int64_t value) : type_(Type::Int) { int_v = value; }
TypedValue(double value) : type_(Type::Double) { double_v = value; }
TypedValue(bool value,
utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::Bool) {
bool_v = value;
}
TypedValue(int value,
utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::Int) {
int_v = value;
}
TypedValue(int64_t value,
utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::Int) {
int_v = value;
}
TypedValue(double value,
utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::Double) {
double_v = value;
}
// conversion function to PropertyValue
explicit operator PropertyValue() const;
// copy constructors for non-primitive types
TypedValue(const std::string &value) : type_(Type::String) {
TypedValue(const std::string &value,
utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::String) {
new (&string_v) std::string(value);
}
TypedValue(const char *value) : type_(Type::String) {
TypedValue(const char *value,
utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::String) {
new (&string_v) std::string(value);
}
TypedValue(const std::vector<TypedValue> &value) : type_(Type::List) {
TypedValue(const std::vector<TypedValue> &value,
utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::List) {
new (&list_v) std::vector<TypedValue>(value);
}
TypedValue(const std::map<std::string, TypedValue> &value)
: type_(Type::Map) {
TypedValue(const std::map<std::string, TypedValue> &value,
utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::Map) {
new (&map_v) std::map<std::string, TypedValue>(value);
}
TypedValue(const VertexAccessor &vertex) : type_(Type::Vertex) {
TypedValue(const VertexAccessor &vertex,
utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::Vertex) {
new (&vertex_v) VertexAccessor(vertex);
}
TypedValue(const EdgeAccessor &edge) : type_(Type::Edge) {
TypedValue(const EdgeAccessor &edge,
utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::Edge) {
new (&edge_v) EdgeAccessor(edge);
}
TypedValue(const Path &path) : type_(Type::Path) { new (&path_v) Path(path); }
TypedValue(const Path &path,
utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::Path) {
new (&path_v) Path(path);
}
/** Construct a copy using default utils::NewDeleteResource() */
TypedValue(const PropertyValue &value);
/** Construct a copy using the given utils::MemoryResource */
TypedValue(const PropertyValue &value, utils::MemoryResource *memory);
// move constructors for non-primitive types
TypedValue(std::string &&value) noexcept : type_(Type::String) {
new (&string_v) std::string(std::move(value));
}
TypedValue(std::string &&value, utils::MemoryResource *memory) noexcept
: memory_(memory), type_(Type::String) {
new (&string_v) std::string(std::move(value));
}
TypedValue(std::vector<TypedValue> &&value) noexcept : type_(Type::List) {
new (&list_v) std::vector<TypedValue>(std::move(value));
}
TypedValue(std::vector<TypedValue> &&value,
utils::MemoryResource *memory) noexcept
: memory_(memory), type_(Type::List) {
new (&list_v) std::vector<TypedValue>(std::move(value));
}
TypedValue(std::map<std::string, TypedValue> &&value) noexcept
: type_(Type::Map) {
new (&map_v) std::map<std::string, TypedValue>(std::move(value));
}
TypedValue(std::map<std::string, TypedValue> &&value,
utils::MemoryResource *memory) noexcept
: memory_(memory), type_(Type::Map) {
new (&map_v) std::map<std::string, TypedValue>(std::move(value));
}
TypedValue(VertexAccessor &&vertex) noexcept : type_(Type::Vertex) {
new (&vertex_v) VertexAccessor(std::move(vertex));
}
TypedValue(VertexAccessor &&vertex, utils::MemoryResource *memory) noexcept
: memory_(memory), type_(Type::Vertex) {
new (&vertex_v) VertexAccessor(std::move(vertex));
}
TypedValue(EdgeAccessor &&edge) noexcept : type_(Type::Edge) {
new (&edge_v) EdgeAccessor(std::move(edge));
}
TypedValue(EdgeAccessor &&edge, utils::MemoryResource *memory) noexcept
: memory_(memory), type_(Type::Edge) {
new (&edge_v) EdgeAccessor(std::move(edge));
}
TypedValue(Path &&path) noexcept : type_(Type::Path) {
new (&path_v) Path(std::move(path));
}
TypedValue(PropertyValue &&value) noexcept;
TypedValue(Path &&path, utils::MemoryResource *memory) noexcept
: memory_(memory), type_(Type::Path) {
new (&path_v) Path(std::move(path));
}
/**
* Construct with the value of other.
* Default utils::NewDeleteResource() is used for allocations. After the move,
* other will be set to Null.
*/
// NOLINTNEXTLINE(hicpp-explicit-conversions)
TypedValue(PropertyValue &&other);
/**
* Construct with the value of other, but use the given utils::MemoryResource.
* After the move, other will be set to Null.
*/
TypedValue(PropertyValue &&other, utils::MemoryResource *memory);
// copy assignment operators
TypedValue &operator=(const char *);
@ -146,7 +274,9 @@ class TypedValue {
TypedValue &operator=(const VertexAccessor &);
TypedValue &operator=(const EdgeAccessor &);
TypedValue &operator=(const Path &);
TypedValue &operator=(const TypedValue &);
/** Copy assign other, utils::MemoryResource of `this` is used */
TypedValue &operator=(const TypedValue &other);
// move assignment operators
TypedValue &operator=(std::string &&) noexcept;
@ -155,7 +285,9 @@ class TypedValue {
TypedValue &operator=(VertexAccessor &&) noexcept;
TypedValue &operator=(EdgeAccessor &&) noexcept;
TypedValue &operator=(Path &&) noexcept;
TypedValue &operator=(TypedValue &&) noexcept;
/** Move assign other, utils::MemoryResource of `this` is used. */
TypedValue &operator=(TypedValue &&other) noexcept;
~TypedValue();
@ -174,7 +306,7 @@ class TypedValue {
template <typename T>
const T &Value() const;
// TODO consider adding getters for primitives by value (and not by ref)
// TODO consider adding getters for primitives by value (and not by ref)
#define DECLARE_VALUE_AND_TYPE_GETTERS(type_param, field) \
/** Gets the value of type field. Throws if value is not field*/ \
@ -207,11 +339,14 @@ class TypedValue {
* PropertyValue */
bool IsPropertyValue() const;
friend std::ostream &operator<<(std::ostream &stream, const TypedValue &prop);
utils::MemoryResource *GetMemoryResource() const { return memory_; }
private:
void DestroyValue();
// Memory resource for allocations of non primitive values
utils::MemoryResource *memory_{utils::NewDeleteResource()};
// storage for the value of the property
union {
bool bool_v;
@ -248,45 +383,209 @@ class TypedValueException : public utils::BasicException {
using utils::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);
// binary bool operators
/**
* Perform logical 'and' on TypedValues.
*
* If any of the values is false, return false. Otherwise checks if any value is
* Null and return Null. All other cases return true. The resulting value uses
* the same MemoryResource as the left hand side arguments.
*
* @throw TypedValueException if arguments are not boolean or Null.
*/
TypedValue operator&&(const TypedValue &a, const TypedValue &b);
/**
* Perform logical 'or' on TypedValues.
*
* If any of the values is true, return true. Otherwise checks if any value is
* Null and return Null. All other cases return false. The resulting value uses
* the same MemoryResource as the left hand side arguments.
*
* @throw TypedValueException if arguments are not boolean or Null.
*/
TypedValue operator||(const TypedValue &a, const TypedValue &b);
/**
* Logically negate a TypedValue.
*
* Negating Null value returns Null. Values other than null raise an exception.
* The resulting value uses the same MemoryResource as the argument.
*
* @throw TypedValueException if TypedValue is not a boolean or Null.
*/
TypedValue operator!(const TypedValue &a);
// arithmetic operators
TypedValue operator-(const TypedValue &a);
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);
// binary bool xor, not power operator
// Be careful: since ^ is binary operator and || and && are logical operators
// they have different priority in c++.
TypedValue operator^(const TypedValue &a, const TypedValue &b);
// stream output
std::ostream &operator<<(std::ostream &os, const TypedValue::Type type);
// comparison operators
/**
* Compare TypedValues and return true, false or Null.
*
* Null is returned if either of the two values is Null.
* Since each TypedValue may have a different MemoryResource for allocations,
* the results is allocated using MemoryResource obtained from the left hand
* side.
*/
TypedValue operator==(const TypedValue &a, const TypedValue &b);
/**
* Compare TypedValues and return true, false or Null.
*
* Null is returned if either of the two values is Null.
* Since each TypedValue may have a different MemoryResource for allocations,
* the results is allocated using MemoryResource obtained from the left hand
* side.
*/
inline TypedValue operator!=(const TypedValue &a, const TypedValue &b) {
return !(a == b);
}
/**
* Compare TypedValues and return true, false or Null.
*
* Null is returned if either of the two values is Null.
* The resulting value uses the same MemoryResource as the left hand side
* argument.
*
* @throw TypedValueException if the values cannot be compared, i.e. they are
* not either Null, numeric or a character string type.
*/
TypedValue operator<(const TypedValue &a, const TypedValue &b);
/**
* Compare TypedValues and return true, false or Null.
*
* Null is returned if either of the two values is Null.
* The resulting value uses the same MemoryResource as the left hand side
* argument.
*
* @throw TypedValueException if the values cannot be compared, i.e. they are
* not either Null, numeric or a character string type.
*/
inline TypedValue operator<=(const TypedValue &a, const TypedValue &b) {
return a < b || a == b;
}
/**
* Compare TypedValues and return true, false or Null.
*
* Null is returned if either of the two values is Null.
* The resulting value uses the same MemoryResource as the left hand side
* argument.
*
* @throw TypedValueException if the values cannot be compared, i.e. they are
* not either Null, numeric or a character string type.
*/
inline TypedValue operator>(const TypedValue &a, const TypedValue &b) {
return !(a <= b);
}
/**
* Compare TypedValues and return true, false or Null.
*
* Null is returned if either of the two values is Null.
* The resulting value uses the same MemoryResource as the left hand side
* argument.
*
* @throw TypedValueException if the values cannot be compared, i.e. they are
* not either Null, numeric or a character string type.
*/
inline TypedValue operator>=(const TypedValue &a, const TypedValue &b) {
return !(a < b);
}
// arithmetic operators
/**
* Arithmetically negate a value.
*
* If the value is Null, then Null is returned.
* The resulting value uses the same MemoryResource as the argument.
*
* @throw TypedValueException if the value is not numeric or Null.
*/
TypedValue operator-(const TypedValue &a);
/**
* Apply the unary plus operator to a value.
*
* If the value is Null, then Null is returned.
* The resulting value uses the same MemoryResource as the argument.
*
* @throw TypedValueException if the value is not numeric or Null.
*/
TypedValue operator+(const TypedValue &a);
/**
* Perform addition or concatenation on two values.
*
* Numeric values are summed, while lists and character strings are
* concatenated. If either value is Null, then Null is returned. The resulting
* value uses the same MemoryResource as the left hand side argument.
*
* @throw TypedValueException if values cannot be summed or concatenated.
*/
TypedValue operator+(const TypedValue &a, const TypedValue &b);
/**
* Subtract two values.
*
* If any of the values is Null, then Null is returned.
* The resulting value uses the same MemoryResource as the left hand side
* argument.
*
* @throw TypedValueException if the values are not numeric or Null.
*/
TypedValue operator-(const TypedValue &a, const TypedValue &b);
/**
* Divide two values.
*
* If any of the values is Null, then Null is returned.
* The resulting value uses the same MemoryResource as the left hand side
* argument.
*
* @throw TypedValueException if the values are not numeric or Null, or if
* dividing two integer values by zero.
*/
TypedValue operator/(const TypedValue &a, const TypedValue &b);
/**
* Multiply two values.
*
* If any of the values is Null, then Null is returned.
* The resulting value uses the same MemoryResource as the left hand side
* argument.
*
* @throw TypedValueException if the values are not numeric or Null.
*/
TypedValue operator*(const TypedValue &a, const TypedValue &b);
/**
* Perform modulo operation on two values.
*
* If any of the values is Null, then Null is returned.
* The resulting value uses the same MemoryResource as the left hand side
* argument.
*
* @throw TypedValueException if the values are not numeric or Null.
*/
TypedValue operator%(const TypedValue &a, const TypedValue &b);
/** Output the TypedValue::Type value as a string */
std::ostream &operator<<(std::ostream &os, const TypedValue::Type &type);
/**
* Output the TypedValue value as a readable string.
* Note that the primary use of this is for debugging and may not yield the
* pretty results you want to display to the user.
*/
std::ostream &operator<<(std::ostream &os, const TypedValue &value);
} // namespace query

View File

@ -429,3 +429,34 @@ TEST_F(TypedValueLogicTest, LogicalXor) {
EXPECT_PROP_EQ(TypedValue(true) ^ TypedValue(false), TypedValue(true));
EXPECT_PROP_EQ(TypedValue(false) ^ TypedValue(false), TypedValue(false));
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST_F(AllTypesFixture, ConstructionWithMemoryResource) {
std::vector<TypedValue> values_with_custom_memory;
utils::MonotonicBufferResource monotonic_memory(1024);
for (const auto &value : values_) {
EXPECT_EQ(value.GetMemoryResource(), utils::NewDeleteResource());
TypedValue copy_constructed_value(value, &monotonic_memory);
EXPECT_EQ(copy_constructed_value.GetMemoryResource(), &monotonic_memory);
values_with_custom_memory.emplace_back(std::move(copy_constructed_value));
const auto &move_constructed_value = values_with_custom_memory.back();
EXPECT_EQ(move_constructed_value.GetMemoryResource(), &monotonic_memory);
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST_F(AllTypesFixture, AssignmentWithMemoryResource) {
std::vector<TypedValue> values_with_default_memory;
utils::MonotonicBufferResource monotonic_memory(1024);
for (const auto &value : values_) {
EXPECT_EQ(value.GetMemoryResource(), utils::NewDeleteResource());
TypedValue copy_assigned_value(&monotonic_memory);
copy_assigned_value = value;
EXPECT_EQ(copy_assigned_value.GetMemoryResource(), &monotonic_memory);
values_with_default_memory.emplace_back(utils::NewDeleteResource());
auto &move_assigned_value = values_with_default_memory.back();
move_assigned_value = std::move(copy_assigned_value);
EXPECT_EQ(move_assigned_value.GetMemoryResource(),
utils::NewDeleteResource());
}
}