diff --git a/include/data_structures/concurrent/skiplist.hpp b/include/data_structures/concurrent/skiplist.hpp index 0462a7686..8bef383bf 100644 --- a/include/data_structures/concurrent/skiplist.hpp +++ b/include/data_structures/concurrent/skiplist.hpp @@ -145,7 +145,11 @@ class SkipList : private Lockable { } static Node *create(const T &item, uint8_t height) { - return create(height, item); + auto node = allocate(height); + + // we have raw memory and we need to construct an object + // of type Node on it + return new (node) Node(item, height); } static Node *create(T &&item, uint8_t height) { @@ -188,6 +192,10 @@ class SkipList : private Lockable { this->data.emplace(std::forward(args)...); } + Node(const T &data, uint8_t height) : Node(height) { + this->data.set(data); + } + Node(T &&data, uint8_t height) : Node(height) { this->data.set(std::move(data)); } @@ -940,6 +948,42 @@ class SkipList : private Lockable { } } + /** + * Insert unique data + * + * F - type of funct which will create new node if needed. Recieves height + * of node. + */ + // TODO this code is not DRY w.r.t. the other insert function (rvalue ref) + std::pair insert(Node *preds[], Node *succs[], const T &data) { + while (true) { + // TODO: before here was data.first + auto level = find_path(this, H - 1, data, preds, succs); + + if (level != -1) { + auto found = succs[level]; + + if (found->flags.is_marked()) continue; + + while (!found->flags.is_fully_linked()) usleep(250); + + return {Iterator{succs[level]}, false}; + } + + auto height = rnd(); + guard_t guards[H]; + + // try to acquire the locks for predecessors up to the height of + // the new node. release the locks and try again if someone else + // has the locks + if (!lock_nodes(height, guards, preds, succs)) continue; + + return {insert_here(Node::create(data, height), preds, succs, + height, guards), + true}; + } + } + /** * Insert unique data * diff --git a/include/storage/model/typed_value.hpp b/include/storage/model/typed_value.hpp new file mode 100644 index 000000000..4a0aaf296 --- /dev/null +++ b/include/storage/model/typed_value.hpp @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "utils/underlying_cast.hpp" +#include "utils/total_ordering.hpp" +#include "utils/exceptions/basic_exception.hpp" + +/** + * Encapsulation of a value and it's type encapsulated in a class that has no + * compiled-time info about that type. + * + * Values can be of a number of predefined types that are enumerated in + * TypedValue::Type. Each such type corresponds to exactly one C++ type. + */ +class TypedValue : public TotalOrdering { + +private: + /** Private default constructor, makes Null */ + TypedValue() : type_(Type::Null) {} + +public: + + /** A value type. Each type corresponds to exactly one C++ type */ + enum class Type : unsigned { + Null, + String, + Bool, + Int, + Float + }; + + // single static reference to Null, used whenever Null should be returned + static const TypedValue Null; + + // constructors for primitive types + TypedValue(bool value) : type_(Type::Bool) { bool_v = value; } + TypedValue(int value) : type_(Type::Int) { int_v = value; } + TypedValue(float value) : type_(Type::Float) { float_v = value; } + + /// constructors for non-primitive types (shared pointers) + TypedValue(const std::string &value) : type_(Type::String) { + new (&string_v) std::shared_ptr(new std::string(value)); + } + TypedValue(const char* value) : type_(Type::String) { + new (&string_v) std::shared_ptr(new std::string(value)); + } + + // assignment ops + TypedValue& operator=(TypedValue& other); + TypedValue& operator=(TypedValue&& other); + + TypedValue(const TypedValue& other); + ~TypedValue(); + + /** + * Returns the value of the property as given type T. + * The behavior of this function is undefined if + * T does not correspond to this property's type_. + * + * @tparam T Type to interpret the value as. + * @return The value as type T. + */ + template T Value() const; + + friend std::ostream& operator<<(std::ostream& stream, const TypedValue& prop); + + /** + * The Type of property. + */ + const Type type_; + +private: + // storage for the value of the property + union { + bool bool_v; + int int_v; + float float_v; + std::shared_ptr string_v; + }; +}; + +/** + * An exception raised by the TypedValue system. Typically when + * trying to perform operations (such as addition) on TypedValues + * of incompatible Types. + */ +class TypedValueException : public BasicException { + +public: + using ::BasicException::BasicException; +}; + +// comparison operators +// they return TypedValue because Null can be returned +TypedValue operator==(const TypedValue& a, const TypedValue& b); +TypedValue operator<(const TypedValue& a, const TypedValue& b); +TypedValue operator!(const TypedValue& a); + +// arithmetic operators +TypedValue operator-(const TypedValue& a); +TypedValue operator+(const TypedValue& a, const TypedValue& b); +TypedValue operator-(const TypedValue& a, const TypedValue& b); +TypedValue operator/(const TypedValue& a, const TypedValue& b); +TypedValue operator*(const TypedValue& a, const TypedValue& b); +TypedValue operator%(const TypedValue& a, const TypedValue& b); + +// binary bool operators +TypedValue operator&&(const TypedValue& a, const TypedValue& b); +TypedValue operator||(const TypedValue& a, const TypedValue& b); + +// stream output +std::ostream &operator<<(std::ostream &os, const TypedValue::Type type); diff --git a/include/storage/model/typed_value_store.hpp b/include/storage/model/typed_value_store.hpp new file mode 100644 index 000000000..efaf00be7 --- /dev/null +++ b/include/storage/model/typed_value_store.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +#include "typed_value.hpp" + +/** + * A collection of properties accessed in a map-like way + * using a key of type Properties::TKey. + * + * The underlying implementation is not necessarily std::map. + */ +class TypedValueStore { +public: + using sptr = std::shared_ptr; + + /** The type of key used to get and set properties */ + using TKey = uint32_t; + + /** + * Returns a TypedValue (by reference) at the given key. + * If the key does not exist, the Null property is returned. + * + * This is NOT thread-safe, the reference might not be valid + * when used in a multithreaded scenario. + * + * @param key The key for which a TypedValue is sought. + * @return See above. + */ + const TypedValue &at(const TKey &key) const; + + /** + * Sets the value for the given key. A new TypedValue instance + * is created for the given value (which is of raw type). + * + * @tparam TValue Type of value. It must be one of the + * predefined possible TypedValue values (bool, string, int...) + * @param key The key for which the property is set. The previous + * value at the same key (if there was one) is replaced. + * @param value The value to set. + */ + template + void set(const TKey &key, const TValue &value); + + /** + * Set overriding for character constants. Forces conversion + * to std::string, otherwise templating might cast the pointer + * to something else (bool) and mess things up. + * + * @param key The key for which the property is set. The previous + * value at the same key (if there was one) is replaced. + * @param value The value to set. + */ + void set(const TKey &key, const char *value); + + /** + * Removes the TypedValue for the given key. + * + * @param key The key for which to remove the property. + * @return The number of removed properties (0 or 1). + */ + size_t erase(const TKey &key); + + /** + * @return The number of Properties in this collection. + */ + size_t size() const; + + + /** + * Accepts two functions. + * + * @param handler Called for each TypedValue in this collection. + * @param finish Called once in the end. + */ + void Accept(std::function handler, + std::function finish = {}) const; + +private: + std::vector> props_; +}; diff --git a/include/storage/model/typed_value_utils.h b/include/storage/model/typed_value_utils.h new file mode 100644 index 000000000..5b5af377f --- /dev/null +++ b/include/storage/model/typed_value_utils.h @@ -0,0 +1,57 @@ +// +// Copyright 2017 Memgraph +// Created by Florijan Stamenkovic on 01.02.17. +// + +#pragma once + +#include +#include "storage/model/typed_value_store.hpp" + + +/** + * Writes all of the values from the given store in JSON format + * to the given output stream. + * + * @param store The store that should be serialized to JSON. + * @param ostream The stream to write to. + */ +void TypedValuesToJson(const TypedValueStore& store, + std::ostream& ostream=std::cout) { + + bool first = true; + + auto write_key = [&ostream, &first](const TypedValueStore::TKey &key) -> std::ostream& { + if (first) { + ostream << '{'; + first = false; + } else + ostream << ','; + + return ostream << '"' << key << "\":"; + }; + + auto handler = [&ostream, &write_key](const TypedValueStore::TKey& key, + const TypedValue& value) { + switch (value.type_) { + case TypedValue::Type::Null: + break; + case TypedValue::Type::Bool: + write_key(key) << (value.Value() ? "true" : "false"); + break; + case TypedValue::Type::String: + write_key(key) << '"' << value.Value() << '"'; + break; + case TypedValue::Type::Int: + write_key(key) << value.Value(); + break; + case TypedValue::Type::Float: + write_key(key) << value.Value(); + break; + } + }; + + auto finish = [&ostream]() { ostream << '}' << std::endl; }; + + store.Accept(handler, finish); +} diff --git a/src/storage/model/typed_value.cpp b/src/storage/model/typed_value.cpp new file mode 100644 index 000000000..eeb4a39fe --- /dev/null +++ b/src/storage/model/typed_value.cpp @@ -0,0 +1,363 @@ +#include +#include +#include +#include + +#include "storage/model/typed_value.hpp" +#include "utils/assert.hpp" + +// Value extraction template instantiations +template<> +bool TypedValue::Value() const { + runtime_assert(type_ == TypedValue::Type::Bool, "Incompatible template param and type"); + return bool_v; +} + +template<> +std::string TypedValue::Value() const { + runtime_assert(type_ == TypedValue::Type::String, "Incompatible template param and type"); + return *string_v; +} + +template<> +int TypedValue::Value() const { + runtime_assert(type_ == TypedValue::Type::Int, "Incompatible template param and type"); + return int_v; +} + +template<> +float TypedValue::Value() const { + runtime_assert(type_ == TypedValue::Type::Float, "Incompatible template param and type"); + return float_v; +} + +TypedValue::TypedValue(const TypedValue &other) : type_(other.type_) { + switch (other.type_) { + case TypedValue::Type::Null: + return; + + case TypedValue::Type::Bool: + this->bool_v = other.bool_v; + return; + + case TypedValue::Type::String: + new(&string_v) std::shared_ptr(other.string_v); + return; + + case Type::Int: + this->int_v = other.int_v; + return; + + case Type::Float: + this->float_v = other.float_v; + return; + } + + permanent_fail("Unsupported TypedValue::Type"); +} + +std::ostream &operator<<(std::ostream &os, const TypedValue::Type type) { + switch (type) { + case TypedValue::Type::Null: + return os << "null"; + case TypedValue::Type::Bool: + return os << "bool"; + case TypedValue::Type::String: + return os << "string"; + case TypedValue::Type::Int: + return os << "int"; + case TypedValue::Type::Float: + return os << "float"; + } + permanent_fail("Unsupported TypedValue::Type"); +} + +std::ostream &operator<<(std::ostream &os, const TypedValue &property) { + switch (property.type_) { + case TypedValue::Type::Null: + return os << "Null"; + case TypedValue::Type::Bool: + return os << (property.Value() ? "true" : "false"); + case TypedValue::Type::String: + return os << property.Value(); + case TypedValue::Type::Int: + return os << property.Value(); + case TypedValue::Type::Float: + return os << property.Value(); + } + permanent_fail("Unsupported TypedValue::Type"); +} + +TypedValue &TypedValue::operator=(TypedValue &&other) { + + // set the type of this + const_cast(type_) = other.type_; + + if (this != &other) { + switch (other.type_) { + case TypedValue::Type::Null: + case TypedValue::Type::Bool: + this->bool_v = other.bool_v; + return *this; + case TypedValue::Type::String: + this->string_v = std::move(other.string_v); + return *this; + case TypedValue::Type::Int: + this->int_v = other.int_v; + return *this; + case TypedValue::Type::Float: + this->float_v = other.float_v; + return *this; + } + } + permanent_fail("Unsupported TypedValue::Type"); +} + +const TypedValue TypedValue::Null = TypedValue(); + +TypedValue::~TypedValue() { + + switch (type_) { + // destructor for primitive types does nothing + case Type::Null: + case Type::Bool: + case Type::Int: + case Type::Float: + return; + + // destructor for shared pointer must release + case Type::String: + string_v.~shared_ptr(); + return; + } + permanent_fail("Unsupported TypedValue::Type"); +} + +/** + * Returns the float value of a property. + * The property MUST be either Float or Int. + * + * @param prop + * @return + */ +float ToFloat(const TypedValue& prop) { + switch (prop.type_) { + case TypedValue::Type::Int: + return (float)prop.Value(); + case TypedValue::Type::Float: + return prop.Value(); + + default: + permanent_fail("Unsupported TypedValue::Type"); + } +} + +TypedValue operator<(const TypedValue& a, const TypedValue& b) { + if (a.type_ == TypedValue::Type::Bool || b.type_ == TypedValue::Type::Bool) + throw TypedValueException("Invalid 'less' operand types({} + {})", a.type_, b.type_); + + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + + if (a.type_ == TypedValue::Type::String || b.type_ == TypedValue::Type::String) { + if (a.type_ != b.type_) + throw TypedValueException("Invalid equality operand types({} + {})", a.type_, b.type_); + else + return a.Value() < b.Value(); + } + + // at this point we only have int and float + if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float) + return ToFloat(a) < ToFloat(b); + else + return a.Value() < b.Value(); +} + +TypedValue operator==(const TypedValue& a, const TypedValue& b) { + + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + + if (a.type_ == TypedValue::Type::String || b.type_ == TypedValue::Type::String) { + if (a.type_ != b.type_) + throw TypedValueException("Invalid equality operand types({} + {})", a.type_, b.type_); + else + return a.Value() == b.Value(); + } + + if (a.type_ == TypedValue::Type::Bool || b.type_ == TypedValue::Type::Bool) { + if (a.type_ != b.type_) + throw TypedValueException("Invalid equality operand types({} + {})", a.type_, b.type_); + else + return a.Value() == b.Value(); + } + // at this point we only have int and float + if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){ + return ToFloat(a) == ToFloat(b); + } else + return a.Value() == b.Value(); + +} + +TypedValue operator!(const TypedValue& a) { + switch (a.type_) { + case TypedValue::Type::Null: + return TypedValue::Null; + case TypedValue::Type::Bool: + return TypedValue(!a.Value()); + + default: + throw TypedValueException("Invalid logical not operand type (!{})", a.type_); + } +} + +/** + * Turns a numeric or string property into a string. + * + * @param prop Property. + * @return A string. + */ +std::string PropToString(const TypedValue& prop) { + switch (prop.type_) { + case TypedValue::Type::String: + return prop.Value(); + case TypedValue::Type::Int: + return std::to_string(prop.Value()); + case TypedValue::Type::Float: + return fmt::format("{}", prop.Value()); + + // unsupported situations + default: + permanent_fail("Unsupported TypedValue::Type"); + } +} + +TypedValue operator-(const TypedValue &a) { + switch (a.type_) { + case TypedValue::Type::Null: + return TypedValue::Null; + case TypedValue::Type::Int: + return -a.Value(); + case TypedValue::Type::Float: + return -a.Value(); + + default: + throw TypedValueException("Invalid unary minus operand type (-{})", a.type_); + } +} + +/** + * Raises a PropertyException if the given properties do not support arithmetic + * operations. If they do, nothing happens. + * + * @param a First prop. + * @param b Second prop. + * @param string_ok If or not for the given operation it's valid to work with + * String values (typically it's OK only for sum). + * @param op_name Name of the operation, used only for exception description, + * if raised. + */ +inline void EnsureArithmeticallyOk(const TypedValue& a, const TypedValue& b, + bool string_ok, const std::string& op_name) { + if (a.type_ == TypedValue::Type::Bool || b.type_ == TypedValue::Type::Bool) + throw TypedValueException("Invalid {} operand types {}, {}", op_name, a.type_, b.type_); + + if (string_ok) + return; + + if (a.type_ == TypedValue::Type::String || b.type_ == TypedValue::Type::String) + throw TypedValueException("Invalid subtraction operands types {}, {}", a.type_, b.type_); +} + +TypedValue operator+(const TypedValue& a, const TypedValue& b){ + EnsureArithmeticallyOk(a, b, true, "addition"); + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + + // no more Bool nor Null, summing works on anything from here onward + + if (a.type_ == TypedValue::Type::String || b.type_ == TypedValue::Type::String) + return PropToString(a) + PropToString(b); + + // at this point we only have int and float + if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){ + return ToFloat(a) + ToFloat(b); + } else + return a.Value() + b.Value(); +} + +TypedValue operator-(const TypedValue& a, const TypedValue& b){ + EnsureArithmeticallyOk(a, b, false, "subtraction"); + + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + + // at this point we only have int and float + if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){ + return ToFloat(a) - ToFloat(b); + } else + return a.Value() - b.Value(); +} + +TypedValue operator/(const TypedValue& a, const TypedValue& b){ + EnsureArithmeticallyOk(a, b, false, "division"); + + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + + // at this point we only have int and float + if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){ + return ToFloat(a) / ToFloat(b); + } else + return a.Value() / b.Value(); +} + +TypedValue operator*(const TypedValue& a, const TypedValue& b){ + EnsureArithmeticallyOk(a, b, false, "multiplication"); + + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + + // at this point we only have int and float + if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){ + return ToFloat(a) * ToFloat(b); + } else + return a.Value() * b.Value(); +} + +TypedValue operator%(const TypedValue& a, const TypedValue& b){ + EnsureArithmeticallyOk(a, b, false, "modulo"); + + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + + // at this point we only have int and float + if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){ + return (float)fmod(ToFloat(a), ToFloat(b)); + } else + return a.Value() % b.Value(); +} + +inline bool IsLogicallyOk(const TypedValue& a) { + return a.type_ == TypedValue::Type::Bool || a.type_ == TypedValue::Type::Null; +} + +TypedValue operator&&(const TypedValue& a, const TypedValue& b) { + if(IsLogicallyOk(a) && IsLogicallyOk(b)){ + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + else + return a.Value() && b.Value(); + } else + throw TypedValueException("Invalid logical and operand types({} && {})", a.type_, b.type_); +} + +TypedValue operator||(const TypedValue& a, const TypedValue& b) { + if(IsLogicallyOk(a) && IsLogicallyOk(b)){ + if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null) + return TypedValue::Null; + else + return a.Value() || b.Value(); + } else + throw TypedValueException("Invalid logical and operand types({} && {})", a.type_, b.type_); +} diff --git a/src/storage/model/typed_value_store.cpp b/src/storage/model/typed_value_store.cpp new file mode 100644 index 000000000..9e538afb8 --- /dev/null +++ b/src/storage/model/typed_value_store.cpp @@ -0,0 +1,68 @@ +#include + +#include "storage/model/typed_value_store.hpp" + +const TypedValue& TypedValueStore::at(const TKey &key) const { + for (const auto& kv : props_) + if (kv.first == key) + return kv.second; + + return TypedValue::Null; +} + +template +void TypedValueStore::set(const TKey &key, const TValue &value) { + for (auto& kv: props_) + if (kv.first == key) { + kv.second = TypedValue(value); + return; + } + + // there is no value for the given key, add new + // TODO consider vector size increment optimization + props_.push_back(std::move(std::make_pair(key, value))); +} + +// instantiations of the TypedValueStore::set function +// instances must be made for all of the supported C++ types +template void TypedValueStore::set(const TKey &key, const std::string &value); +template void TypedValueStore::set(const TKey &key, const bool &value); +template void TypedValueStore::set(const TKey &key, const int &value); +template void TypedValueStore::set(const TKey &key, const float &value); + +/** + * Set overriding for character constants. Forces conversion + * to std::string, otherwise templating might cast the pointer + * to something else (bool) and mess things up. + * + * @param key The key for which the property is set. The previous + * value at the same key (if there was one) is replaced. + * @param value The value to set. + */ +void TypedValueStore::set(const TKey &key, const char *value) { + set(key, std::string(value)); +} + +size_t TypedValueStore::erase(const TKey &key) { + auto found = std::find_if(props_.begin(), props_.end(), [&key](std::pair &kv){return kv.first == key;}); + if (found != props_.end()) { + props_.erase(found); + return 1; + } + return 0; +} + +size_t TypedValueStore::size() const { return props_.size(); } + +void TypedValueStore::Accept(std::function handler, + std::function finish) const { + if (handler) + for (const auto& prop : props_) + handler(prop.first, prop.second); + + if (finish) + finish(); + +} + + diff --git a/tests/concurrent/sl_set.cpp b/tests/concurrent/sl_set.cpp index 883e0e02d..a287c7a0b 100644 --- a/tests/concurrent/sl_set.cpp +++ b/tests/concurrent/sl_set.cpp @@ -15,7 +15,7 @@ int main() init_log(); memory_check(THREADS_NO, [] { - set_t skiplist; + ConcurrentSet skiplist; auto futures = run>( THREADS_NO, skiplist, [](auto acc, auto index) { @@ -25,14 +25,16 @@ int main() std::vector set(key_range); do { - size_t num = rand(); + int num = rand(); + std::string num_str = std::to_string(num); if (rand_op()) { - if (acc.remove(num)) { + if (acc.remove(num_str)) { downcount--; set[num]--; } } else { - if (acc.insert(num).second) { + std::string num_str = std::to_string(num); + if (acc.insert(num_str).second) { downcount--; set[num]++; } @@ -52,12 +54,12 @@ int main() auto accessor = skiplist.access(); for (int i = 0; i < key_range; i++) { permanent_assert(set[i] == 0 || set[i] == 1 || - (set[i] == 1) ^ accessor.contains(i), + (set[i] == 1) ^ accessor.contains(std::to_string(i)), "Set doesn't hold it's guarantees."); } for (auto &e : accessor) { - set[e]--; + set[std::stoi(e)]--; } check_zero(key_range, set, "Set");