/* * Copyright (C) 2015 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 "mutation.hh" #include "clustering_key_filter.hh" #include "core/future.hh" #include "core/future-util.hh" #include "core/do_with.hh" #include "tracing/trace_state.hh" // A mutation_reader is an object which allows iterating on mutations: invoke // the function to get a future for the next mutation, with an unset optional // marking the end of iteration. After calling mutation_reader's operator(), // caller must keep the object alive until the returned future is fulfilled. // // streamed_mutation object emitted by mutation_reader remains valid after the // destruction of the mutation_reader. // // Asking mutation_reader for another streamed_mutation (i.e. invoking // mutation_reader::operator()) invalidates all streamed_mutation objects // previously produced by that reader. // // The mutations returned have strictly monotonically increasing keys. Two // consecutive mutations never have equal keys. // // TODO: When iterating over mutations, we don't need a schema_ptr for every // single one as it is normally the same for all of them. So "mutation" might // not be the optimal object to use here. class mutation_reader final { public: class impl { public: virtual ~impl() {} virtual future operator()() = 0; virtual future<> fast_forward_to(const dht::partition_range&) { throw std::bad_function_call(); } }; private: class null_impl final : public impl { public: virtual future operator()() override { throw std::bad_function_call(); } }; private: std::unique_ptr _impl; public: mutation_reader(std::unique_ptr impl) noexcept : _impl(std::move(impl)) {} mutation_reader() : mutation_reader(std::make_unique()) {} mutation_reader(mutation_reader&&) = default; mutation_reader(const mutation_reader&) = delete; mutation_reader& operator=(mutation_reader&&) = default; mutation_reader& operator=(const mutation_reader&) = delete; future operator()() { return _impl->operator()(); } // Changes the range of partitions to pr. The range can only be moved // forwards. pr.begin() needs to be larger than pr.end() of the previousl // used range (i.e. either the initial one passed to the constructor or a // previous fast forward target). // pr needs to be valid until the reader is destroyed or fast_forward_to() // is called again. future<> fast_forward_to(const dht::partition_range& pr) { return _impl->fast_forward_to(pr); } }; // Impl: derived from mutation_reader::impl; Args/args: arguments for Impl's constructor template inline mutation_reader make_mutation_reader(Args&&... args) { return mutation_reader(std::make_unique(std::forward(args)...)); } // Combines multiple mutation_readers into one. class combined_mutation_reader : public mutation_reader::impl { std::vector _readers; std::vector _all_readers; struct mutation_and_reader { streamed_mutation m; mutation_reader* read; bool operator<(const mutation_and_reader& other) const { return read < other.read; } struct less_compare { bool operator()(const mutation_and_reader& a, mutation_reader* b) const { return a.read < b; } bool operator()(mutation_reader* a, const mutation_and_reader& b) const { return a < b.read; } bool operator()(const mutation_and_reader& a, const mutation_and_reader& b) const { return a < b; } }; }; std::vector _ptables; // comparison function for std::make_heap()/std::push_heap() static bool heap_compare(const mutation_and_reader& a, const mutation_and_reader& b) { auto&& s = a.m.schema(); // order of comparison is inverted, because heaps produce greatest value first return b.m.decorated_key().less_compare(*s, a.m.decorated_key()); } std::vector _current; std::vector _next; private: future<> prepare_next(); // Produces next mutation or disengaged optional if there are no more. future next(); protected: combined_mutation_reader() = default; void init_mutation_reader_set(std::vector); future<> fast_forward_to(std::vector to_add, std::vector to_remove, const dht::partition_range& pr); public: combined_mutation_reader(std::vector readers); virtual future operator()() override; virtual future<> fast_forward_to(const dht::partition_range& pr) override; }; // Creates a mutation reader which combines data return by supplied readers. // Returns mutation of the same schema only when all readers return mutations // of the same schema. mutation_reader make_combined_reader(std::vector); mutation_reader make_combined_reader(mutation_reader&& a, mutation_reader&& b); // reads from the input readers, in order mutation_reader make_reader_returning(mutation); mutation_reader make_reader_returning(streamed_mutation); mutation_reader make_reader_returning_many(std::vector, const query::partition_slice& slice = query::full_slice, streamed_mutation::forwarding fwd = streamed_mutation::forwarding::no); mutation_reader make_reader_returning_many(std::vector, const dht::partition_range&); mutation_reader make_reader_returning_many(std::vector); mutation_reader make_empty_reader(); struct restricted_mutation_reader_config { semaphore* sem = nullptr; std::chrono::nanoseconds timeout = {}; size_t max_queue_length = std::numeric_limits::max(); std::function raise_queue_overloaded_exception = default_raise_queue_overloaded_exception; static void default_raise_queue_overloaded_exception() { throw std::runtime_error("restricted mutation reader queue overload"); } }; // Restricts a given `mutation_reader` to a concurrency limited according to settings in // a restricted_mutation_reader_config. These settings include a semaphore for limiting the number // of active concurrent readers, a timeout for inactive readers, and a maximum queue size for // inactive readers. mutation_reader make_restricted_reader(const restricted_mutation_reader_config& config, unsigned weight, mutation_reader&& base); /* template concept bool StreamedMutationFilter() { return requires(T t, const streamed_mutation& sm) { { t(sm) } -> bool; }; } */ template class filtering_reader : public mutation_reader::impl { mutation_reader _rd; MutationFilter _filter; streamed_mutation_opt _current; static_assert(std::is_same>::value, "bad MutationFilter signature"); public: filtering_reader(mutation_reader rd, MutationFilter&& filter) : _rd(std::move(rd)), _filter(std::forward(filter)) { } virtual future operator()() override {\ return repeat([this] { return _rd().then([this] (streamed_mutation_opt&& mo) mutable { if (!mo) { _current = std::move(mo); return stop_iteration::yes; } else { if (_filter(*mo)) { _current = std::move(mo); return stop_iteration::yes; } return stop_iteration::no; } }); }).then([this] { return make_ready_future(std::move(_current)); }); }; virtual future<> fast_forward_to(const dht::partition_range& pr) override { return _rd.fast_forward_to(pr); } }; // Creates a mutation_reader wrapper which creates a new stream of mutations // with some mutations removed from the original stream. // MutationFilter is a callable which decides which mutations are dropped. It // accepts mutation const& and returns a bool. The mutation stays in the // stream if and only if the filter returns true. template mutation_reader make_filtering_reader(mutation_reader rd, MutationFilter&& filter) { return make_mutation_reader>(std::move(rd), std::forward(filter)); } // Calls the consumer for each element of the reader's stream until end of stream // is reached or the consumer requests iteration to stop by returning stop_iteration::yes. // The consumer should accept mutation as the argument and return stop_iteration. // The returned future<> resolves when consumption ends. template inline future<> consume(mutation_reader& reader, Consumer consumer) { static_assert(std::is_same, futurize_t>>::value, "bad Consumer signature"); using futurator = futurize>; return do_with(std::move(consumer), [&reader] (Consumer& c) -> future<> { return repeat([&reader, &c] () { return reader().then([] (auto sm) { return mutation_from_streamed_mutation(std::move(sm)); }).then([&c] (mutation_opt&& mo) -> future { if (!mo) { return make_ready_future(stop_iteration::yes); } return futurator::apply(c, std::move(*mo)); }); }); }); } // mutation_source represents source of data in mutation form. The data source // can be queried multiple times and in parallel. For each query it returns // independent mutation_reader. // The reader returns mutations having all the same schema, the one passed // when invoking the source. class mutation_source { using partition_range = const dht::partition_range&; using io_priority = const io_priority_class&; using func_type = std::function; // We could have our own version of std::function<> that is nothrow // move constructible and save some indirection and allocation. // Probably not worth the effort though. std::unique_ptr _fn; private: mutation_source() = default; explicit operator bool() const { return bool(_fn); } friend class optimized_optional; public: mutation_source(func_type fn) : _fn(std::make_unique(std::move(fn))) {} mutation_source(std::function fn) : _fn(std::make_unique([fn = std::move(fn)] (schema_ptr s, partition_range range, const query::partition_slice& slice, io_priority pc, tracing::trace_state_ptr, streamed_mutation::forwarding) { return fn(s, range, slice, pc); })) {} mutation_source(std::function fn) : _fn(std::make_unique([fn = std::move(fn)] (schema_ptr s, partition_range range, const query::partition_slice& slice, io_priority, tracing::trace_state_ptr, streamed_mutation::forwarding) { return fn(s, range, slice); })) {} mutation_source(std::function fn) : _fn(std::make_unique([fn = std::move(fn)] (schema_ptr s, partition_range range, const query::partition_slice&, io_priority, tracing::trace_state_ptr, streamed_mutation::forwarding) { return fn(s, range); })) {} mutation_source(const mutation_source& other) : _fn(std::make_unique(*other._fn)) { } mutation_source& operator=(const mutation_source& other) { _fn = std::make_unique(*other._fn); return *this; } mutation_source(mutation_source&&) = default; mutation_source& operator=(mutation_source&&) = default; // Creates a new reader. // // All parameters captured by reference must remain live as long as returned // mutation_reader or streamed_mutation obtained through it are alive. mutation_reader operator()(schema_ptr s, partition_range range = query::full_partition_range, const query::partition_slice& slice = query::full_slice, io_priority pc = default_priority_class(), tracing::trace_state_ptr trace_state = nullptr, streamed_mutation::forwarding fwd = streamed_mutation::forwarding::no) const { return (*_fn)(std::move(s), range, slice, pc, std::move(trace_state), fwd); } }; template<> struct move_constructor_disengages { enum { value = true }; }; using mutation_source_opt = optimized_optional; /// A partition_presence_checker quickly returns whether a key is known not to exist /// in a data source (it may return false positives, but not false negatives). enum class partition_presence_checker_result { definitely_doesnt_exist, maybe_exists }; using partition_presence_checker = std::function; inline partition_presence_checker make_default_partition_presence_checker() { return [] (const dht::decorated_key&) { return partition_presence_checker_result::maybe_exists; }; } template future do_consume_streamed_mutation_flattened(streamed_mutation& sm, Consumer& c) { do { if (sm.is_buffer_empty()) { if (sm.is_end_of_stream()) { break; } auto f = sm.fill_buffer(); if (!f.available()) { return f.then([&] { return do_consume_streamed_mutation_flattened(sm, c); }); } f.get(); } else { if (sm.pop_mutation_fragment().consume(c) == stop_iteration::yes) { break; } } } while (true); return make_ready_future(c.consume_end_of_partition()); } /* template concept bool FlattenedConsumer() { return StreamedMutationConsumer() && requires(T obj, const dht::decorated_key& dk) { obj.consume_new_partition(dk); obj.consume_end_of_partition(); }; } */ template auto consume_flattened(mutation_reader mr, FlattenedConsumer&& c, bool reverse_mutations = false) { return do_with(std::move(mr), std::move(c), stdx::optional(), [reverse_mutations] (auto& mr, auto& c, auto& sm) { return repeat([&, reverse_mutations] { return mr().then([&, reverse_mutations] (auto smopt) { if (!smopt) { return make_ready_future(stop_iteration::yes); } if (!reverse_mutations) { sm.emplace(std::move(*smopt)); } else { sm.emplace(reverse_streamed_mutation(std::move(*smopt))); } c.consume_new_partition(sm->decorated_key()); if (sm->partition_tombstone()) { c.consume(sm->partition_tombstone()); } return do_consume_streamed_mutation_flattened(*sm, c); }); }).then([&] { return c.consume_end_of_stream(); }); }); } /* template concept bool StreamedMutationFilter() { return requires(T obj, const streamed_mutation& sm) { { filter(sm); } -> bool; }; } */ // This version of consume_flattened() must be run inside a thread and // guarantees that all FlattenedConsumer functions will also be called in the same thread // context. template auto consume_flattened_in_thread(mutation_reader& mr, FlattenedConsumer& c, StreamedMutationFilter&& filter) { while (true) { auto smopt = mr().get0(); if (!smopt) { break; } auto& sm = *smopt; if (!filter(sm)) { continue; } c.consume_new_partition(sm.decorated_key()); if (sm.partition_tombstone()) { c.consume(sm.partition_tombstone()); } do { if (sm.is_buffer_empty()) { if (sm.is_end_of_stream()) { break; } sm.fill_buffer().get0(); } else { if (sm.pop_mutation_fragment().consume(c) == stop_iteration::yes) { break; } } } while (true); if (c.consume_end_of_partition() == stop_iteration::yes) { break; } } return c.consume_end_of_stream(); } template auto consume_flattened_in_thread(mutation_reader& mr, FlattenedConsumer& c) { return consume_flattened_in_thread(mr, c, [] (auto&&) { return true; }); } // Adapts a non-movable FlattenedConsumer to a movable one. template class stable_flattened_mutations_consumer { std::unique_ptr _ptr; public: stable_flattened_mutations_consumer(std::unique_ptr ptr) : _ptr(std::move(ptr)) {} auto consume_new_partition(const dht::decorated_key& dk) { return _ptr->consume_new_partition(dk); } auto consume(tombstone t) { return _ptr->consume(t); } auto consume(static_row&& sr) { return _ptr->consume(std::move(sr)); } auto consume(clustering_row&& cr) { return _ptr->consume(std::move(cr)); } auto consume(range_tombstone&& rt) { return _ptr->consume(std::move(rt)); } auto consume_end_of_partition() { return _ptr->consume_end_of_partition(); } auto consume_end_of_stream() { return _ptr->consume_end_of_stream(); } }; template stable_flattened_mutations_consumer make_stable_flattened_mutations_consumer(Args&&... args) { return { std::make_unique(std::forward(args)...) }; } // Requires ranges to be sorted and disjoint. mutation_reader make_multi_range_reader(schema_ptr s, mutation_source source, const dht::partition_range_vector& ranges, const query::partition_slice& slice, const io_priority_class& pc = default_priority_class(), tracing::trace_state_ptr trace_state = nullptr, streamed_mutation::forwarding fwd = streamed_mutation::forwarding::no);