/* * Copyright (C) 2016 ScyllaDB */ /* * This file is part of Scylla. * * Scylla is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Scylla is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Scylla. If not, see . */ #pragma once #include #include "atomic_cell_or_collection.hh" #include "types.hh" #include "stdx.hh" class mutation; class mutation; class counter_id { int64_t _least_significant; int64_t _most_significant; public: counter_id() = default; explicit counter_id(utils::UUID uuid) noexcept : _least_significant(uuid.get_least_significant_bits()) , _most_significant(uuid.get_most_significant_bits()) { } utils::UUID to_uuid() const { return utils::UUID(_most_significant, _least_significant); } bool operator<(const counter_id& other) const { return to_uuid() < other.to_uuid(); } bool operator==(const counter_id& other) const { return to_uuid() == other.to_uuid(); } bool operator!=(const counter_id& other) const { return !(*this == other); } public: static counter_id local(); // For tests. static counter_id generate_random() { return counter_id(utils::make_random_uuid()); } }; static_assert(std::is_pod::value, "counter_id should be a POD type"); std::ostream& operator<<(std::ostream& os, const counter_id& id); template class basic_counter_shard_view { enum class offset : unsigned { id = 0u, value = unsigned(id) + sizeof(counter_id), logical_clock = unsigned(value) + sizeof(int64_t), total_size = unsigned(logical_clock) + sizeof(int64_t), }; private: typename View::pointer _base; private: template T read(offset off) const { T value; std::copy_n(_base + static_cast(off), sizeof(T), reinterpret_cast(&value)); return value; } public: static constexpr auto size = size_t(offset::total_size); public: basic_counter_shard_view() = default; explicit basic_counter_shard_view(typename View::pointer ptr) noexcept : _base(ptr) { } counter_id id() const { return read(offset::id); } int64_t value() const { return read(offset::value); } int64_t logical_clock() const { return read(offset::logical_clock); } void swap_value_and_clock(basic_counter_shard_view& other) noexcept { static constexpr size_t off = size_t(offset::value); static constexpr size_t size = size_t(offset::total_size) - off; typename View::value_type tmp[size]; std::copy_n(_base + off, size, tmp); std::copy_n(other._base + off, size, _base + off); std::copy_n(tmp, size, other._base + off); } void set_value_and_clock(const basic_counter_shard_view& other) noexcept { static constexpr size_t off = size_t(offset::value); static constexpr size_t size = size_t(offset::total_size) - off; std::copy_n(other._base + off, size, _base + off); } bool operator==(const basic_counter_shard_view& other) const { return id() == other.id() && value() == other.value() && logical_clock() == other.logical_clock(); } bool operator!=(const basic_counter_shard_view& other) const { return !(*this == other); } struct less_compare_by_id { bool operator()(const basic_counter_shard_view& x, const basic_counter_shard_view& y) const { return x.id() < y.id(); } }; }; using counter_shard_view = basic_counter_shard_view; std::ostream& operator<<(std::ostream& os, counter_shard_view csv); class counter_shard { counter_id _id; int64_t _value; int64_t _logical_clock; private: template static void write(const T& value, bytes::iterator& out) { out = std::copy_n(reinterpret_cast(&value), sizeof(T), out); } public: counter_shard(counter_id id, int64_t value, int64_t logical_clock) noexcept : _id(id) , _value(value) , _logical_clock(logical_clock) { } explicit counter_shard(counter_shard_view csv) noexcept : _id(csv.id()) , _value(csv.value()) , _logical_clock(csv.logical_clock()) { } counter_id id() const { return _id; } int64_t value() const { return _value; } int64_t logical_clock() const { return _logical_clock; } counter_shard& update(int64_t value_delta, int64_t clock_increment) noexcept { _value += value_delta; _logical_clock += clock_increment; return *this; } counter_shard& apply(counter_shard_view other) noexcept { auto other_clock = other.logical_clock(); if (_logical_clock < other_clock) { _logical_clock = other_clock; _value = other.value(); } return *this; } static size_t serialized_size() { return counter_shard_view::size; } void serialize(bytes::iterator& out) const { write(_id, out); write(_value, out); write(_logical_clock, out); } }; class counter_cell_builder { std::vector _shards; public: counter_cell_builder() = default; counter_cell_builder(size_t shard_count) { _shards.reserve(shard_count); } void add_shard(const counter_shard& cs) { _shards.emplace_back(cs); } size_t serialized_size() const { return _shards.size() * counter_shard::serialized_size(); } void serialize(bytes::iterator& out) const { for (auto&& cs : _shards) { cs.serialize(out); } } bool empty() const { return _shards.empty(); } atomic_cell build(api::timestamp_type timestamp) const { return atomic_cell::make_live_from_serializer(timestamp, serialized_size(), [this] (bytes::iterator out) { serialize(out); }); } static atomic_cell from_single_shard(api::timestamp_type timestamp, const counter_shard& cs) { return atomic_cell::make_live_from_serializer(timestamp, counter_shard::serialized_size(), [&cs] (bytes::iterator out) { cs.serialize(out); }); } class inserter_iterator : public std::iterator { counter_cell_builder* _builder; public: explicit inserter_iterator(counter_cell_builder& b) : _builder(&b) { } inserter_iterator& operator=(const counter_shard& cs) { _builder->add_shard(cs); return *this; } inserter_iterator& operator=(const counter_shard_view& csv) { return operator=(counter_shard(csv)); } inserter_iterator& operator++() { return *this; } inserter_iterator& operator++(int) { return *this; } inserter_iterator& operator*() { return *this; }; }; inserter_iterator inserter() { return inserter_iterator(*this); } }; // := // := // := * template class basic_counter_cell_view { protected: atomic_cell_base _cell; private: class shard_iterator : public std::iterator> { typename View::pointer _current; basic_counter_shard_view _current_view; public: shard_iterator() = default; shard_iterator(typename View::pointer ptr) noexcept : _current(ptr), _current_view(ptr) { } basic_counter_shard_view& operator*() noexcept { return _current_view; } basic_counter_shard_view* operator->() noexcept { return &_current_view; } shard_iterator& operator++() noexcept { _current += counter_shard_view::size; _current_view = basic_counter_shard_view(_current); return *this; } shard_iterator operator++(int) noexcept { auto it = *this; operator++(); return it; } shard_iterator& operator--() noexcept { _current -= counter_shard_view::size; _current_view = basic_counter_shard_view(_current); return *this; } shard_iterator operator--(int) noexcept { auto it = *this; operator--(); return it; } bool operator==(const shard_iterator& other) const noexcept { return _current == other._current; } bool operator!=(const shard_iterator& other) const noexcept { return !(*this == other); } }; public: boost::iterator_range shards() const { auto bv = _cell.value(); auto begin = shard_iterator(bv.data()); auto end = shard_iterator(bv.data() + bv.size()); return boost::make_iterator_range(begin, end); } size_t shard_count() const { return _cell.value().size() / counter_shard_view::size; } public: // ac must be a live counter cell explicit basic_counter_cell_view(atomic_cell_base ac) noexcept : _cell(ac) { assert(_cell.is_live()); assert(!_cell.is_counter_update()); } api::timestamp_type timestamp() const { return _cell.timestamp(); } static data_type total_value_type() { return long_type; } int64_t total_value() const { return boost::accumulate(shards(), int64_t(0), [] (int64_t v, counter_shard_view cs) { return v + cs.value(); }); } stdx::optional get_shard(const counter_id& id) const { auto it = boost::range::find_if(shards(), [&id] (counter_shard_view csv) { return csv.id() == id; }); if (it == shards().end()) { return { }; } return *it; } stdx::optional local_shard() const { // TODO: consider caching local shard position return get_shard(counter_id::local()); } bool operator==(const basic_counter_cell_view& other) const { return timestamp() == other.timestamp() && boost::equal(shards(), other.shards()); } }; struct counter_cell_view : basic_counter_cell_view { using basic_counter_cell_view::basic_counter_cell_view; // Reversibly applies two counter cells, at least one of them must be live. // Returns true iff dst was modified. static bool apply_reversibly(atomic_cell_or_collection& dst, atomic_cell_or_collection& src); // Reverts apply performed by apply_reversible(). static void revert_apply(atomic_cell_or_collection& dst, atomic_cell_or_collection& src); // Computes a counter cell containing minimal amount of data which, when // applied to 'b' returns the same cell as 'a' and 'b' applied together. static stdx::optional difference(atomic_cell_view a, atomic_cell_view b); friend std::ostream& operator<<(std::ostream& os, counter_cell_view ccv); }; struct counter_cell_mutable_view : basic_counter_cell_view { using basic_counter_cell_view::basic_counter_cell_view; void set_timestamp(api::timestamp_type ts) { _cell.set_timestamp(ts); } }; // Transforms mutation dst from counter updates to counter shards using state // stored in current_state. // If current_state is present it has to be in the same schema as dst. void transform_counter_updates_to_shards(mutation& dst, const mutation* current_state, uint64_t clock_offset); template<> struct appending_hash { template void operator()(Hasher& h, const counter_shard_view& cshard) const { ::feed_hash(h, cshard.id().to_uuid()); ::feed_hash(h, cshard.value()); ::feed_hash(h, cshard.logical_clock()); } }; template<> struct appending_hash { template void operator()(Hasher& h, const counter_cell_view& cell) const { ::feed_hash(h, true); // is_live ::feed_hash(h, cell.timestamp()); for (auto&& csv : cell.shards()) { ::feed_hash(h, csv); } } };