diff --git a/interval.hh b/interval.hh
new file mode 100644
index 0000000000..4928c6453f
--- /dev/null
+++ b/interval.hh
@@ -0,0 +1,728 @@
+/*
+ * 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
+#include
+#include
+#include
+#include
+#include
+
+template
+class interval_bound {
+ T _value;
+ bool _inclusive;
+public:
+ interval_bound(T value, bool inclusive = true)
+ : _value(std::move(value))
+ , _inclusive(inclusive)
+ { }
+ const T& value() const & { return _value; }
+ T&& value() && { return std::move(_value); }
+ bool is_inclusive() const { return _inclusive; }
+ bool operator==(const interval_bound& other) const {
+ return (_value == other._value) && (_inclusive == other._inclusive);
+ }
+ template
+ bool equal(const interval_bound& other, Comparator&& cmp) const {
+ return _inclusive == other._inclusive && cmp(_value, other._value) == 0;
+ }
+};
+
+template
+class nonwrapping_interval;
+
+template
+using interval = nonwrapping_interval;
+
+// An interval which can have inclusive, exclusive or open-ended bounds on each end.
+// The end bound can be smaller than the start bound.
+template
+class wrapping_interval {
+ template
+ using optional = std::optional;
+public:
+ using bound = interval_bound;
+
+ template
+ using transformed_type = typename std::remove_cv_t>>;
+private:
+ optional _start;
+ optional _end;
+ bool _singular;
+public:
+ wrapping_interval(optional start, optional end, bool singular = false)
+ : _start(std::move(start))
+ , _singular(singular) {
+ if (!_singular) {
+ _end = std::move(end);
+ }
+ }
+ wrapping_interval(T value)
+ : _start(bound(std::move(value), true))
+ , _end()
+ , _singular(true)
+ { }
+ wrapping_interval() : wrapping_interval({}, {}) { }
+private:
+ // Bound wrappers for compile-time dispatch and safety.
+ struct start_bound_ref { const optional& b; };
+ struct end_bound_ref { const optional& b; };
+
+ start_bound_ref start_bound() const { return { start() }; }
+ end_bound_ref end_bound() const { return { end() }; }
+
+ template
+ static bool greater_than_or_equal(end_bound_ref end, start_bound_ref start, Comparator&& cmp) {
+ return !end.b || !start.b || cmp(end.b->value(), start.b->value())
+ >= (!end.b->is_inclusive() || !start.b->is_inclusive());
+ }
+
+ template
+ static bool less_than(end_bound_ref end, start_bound_ref start, Comparator&& cmp) {
+ return !greater_than_or_equal(end, start, cmp);
+ }
+
+ template
+ static bool less_than_or_equal(start_bound_ref first, start_bound_ref second, Comparator&& cmp) {
+ return !first.b || (second.b && cmp(first.b->value(), second.b->value())
+ <= -(!first.b->is_inclusive() && second.b->is_inclusive()));
+ }
+
+ template
+ static bool less_than(start_bound_ref first, start_bound_ref second, Comparator&& cmp) {
+ return second.b && (!first.b || cmp(first.b->value(), second.b->value())
+ < (first.b->is_inclusive() && !second.b->is_inclusive()));
+ }
+
+ template
+ static bool greater_than_or_equal(end_bound_ref first, end_bound_ref second, Comparator&& cmp) {
+ return !first.b || (second.b && cmp(first.b->value(), second.b->value())
+ >= (!first.b->is_inclusive() && second.b->is_inclusive()));
+ }
+public:
+ // the point is before the interval (works only for non wrapped intervals)
+ // Comparator must define a total ordering on T.
+ template
+ bool before(const T& point, Comparator&& cmp) const {
+ assert(!is_wrap_around(cmp));
+ if (!start()) {
+ return false; //open start, no points before
+ }
+ auto r = cmp(point, start()->value());
+ if (r < 0) {
+ return true;
+ }
+ if (!start()->is_inclusive() && r == 0) {
+ return true;
+ }
+ return false;
+ }
+ // the point is after the interval (works only for non wrapped intervals)
+ // Comparator must define a total ordering on T.
+ template
+ bool after(const T& point, Comparator&& cmp) const {
+ assert(!is_wrap_around(cmp));
+ if (!end()) {
+ return false; //open end, no points after
+ }
+ auto r = cmp(end()->value(), point);
+ if (r < 0) {
+ return true;
+ }
+ if (!end()->is_inclusive() && r == 0) {
+ return true;
+ }
+ return false;
+ }
+ // check if two intervals overlap.
+ // Comparator must define a total ordering on T.
+ template
+ bool overlaps(const wrapping_interval& other, Comparator&& cmp) const {
+ bool this_wraps = is_wrap_around(cmp);
+ bool other_wraps = other.is_wrap_around(cmp);
+
+ if (this_wraps && other_wraps) {
+ return true;
+ } else if (this_wraps) {
+ auto unwrapped = unwrap();
+ return other.overlaps(unwrapped.first, cmp) || other.overlaps(unwrapped.second, cmp);
+ } else if (other_wraps) {
+ auto unwrapped = other.unwrap();
+ return overlaps(unwrapped.first, cmp) || overlaps(unwrapped.second, cmp);
+ }
+
+ // No interval should reach this point as wrap around.
+ assert(!this_wraps);
+ assert(!other_wraps);
+
+ // if both this and other have an open start, the two intervals will overlap.
+ if (!start() && !other.start()) {
+ return true;
+ }
+
+ return greater_than_or_equal(end_bound(), other.start_bound(), cmp)
+ && greater_than_or_equal(other.end_bound(), start_bound(), cmp);
+ }
+ static wrapping_interval make(bound start, bound end) {
+ return wrapping_interval({std::move(start)}, {std::move(end)});
+ }
+ static wrapping_interval make_open_ended_both_sides() {
+ return {{}, {}};
+ }
+ static wrapping_interval make_singular(T value) {
+ return {std::move(value)};
+ }
+ static wrapping_interval make_starting_with(bound b) {
+ return {{std::move(b)}, {}};
+ }
+ static wrapping_interval make_ending_with(bound b) {
+ return {{}, {std::move(b)}};
+ }
+ bool is_singular() const {
+ return _singular;
+ }
+ bool is_full() const {
+ return !_start && !_end;
+ }
+ void reverse() {
+ if (!_singular) {
+ std::swap(_start, _end);
+ }
+ }
+ const optional& start() const {
+ return _start;
+ }
+ const optional& end() const {
+ return _singular ? _start : _end;
+ }
+ // Range is a wrap around if end value is smaller than the start value
+ // or they're equal and at least one bound is not inclusive.
+ // Comparator must define a total ordering on T.
+ template
+ bool is_wrap_around(Comparator&& cmp) const {
+ if (_end && _start) {
+ auto r = cmp(end()->value(), start()->value());
+ return r < 0
+ || (r == 0 && (!start()->is_inclusive() || !end()->is_inclusive()));
+ } else {
+ return false; // open ended interval or singular interval don't wrap around
+ }
+ }
+ // Converts a wrap-around interval to two non-wrap-around intervals.
+ // The returned intervals are not overlapping and ordered.
+ // Call only when is_wrap_around().
+ std::pair unwrap() const {
+ return {
+ { {}, end() },
+ { start(), {} }
+ };
+ }
+ // the point is inside the interval
+ // Comparator must define a total ordering on T.
+ template
+ bool contains(const T& point, Comparator&& cmp) const {
+ if (is_wrap_around(cmp)) {
+ auto unwrapped = unwrap();
+ return unwrapped.first.contains(point, cmp)
+ || unwrapped.second.contains(point, cmp);
+ } else {
+ return !before(point, cmp) && !after(point, cmp);
+ }
+ }
+ // Returns true iff all values contained by other are also contained by this.
+ // Comparator must define a total ordering on T.
+ template
+ bool contains(const wrapping_interval& other, Comparator&& cmp) const {
+ bool this_wraps = is_wrap_around(cmp);
+ bool other_wraps = other.is_wrap_around(cmp);
+
+ if (this_wraps && other_wraps) {
+ return cmp(start()->value(), other.start()->value())
+ <= -(!start()->is_inclusive() && other.start()->is_inclusive())
+ && cmp(end()->value(), other.end()->value())
+ >= (!end()->is_inclusive() && other.end()->is_inclusive());
+ }
+
+ if (!this_wraps && !other_wraps) {
+ return less_than_or_equal(start_bound(), other.start_bound(), cmp)
+ && greater_than_or_equal(end_bound(), other.end_bound(), cmp);
+ }
+
+ if (other_wraps) { // && !this_wraps
+ return !start() && !end();
+ }
+
+ // !other_wraps && this_wraps
+ return (other.start() && cmp(start()->value(), other.start()->value())
+ <= -(!start()->is_inclusive() && other.start()->is_inclusive()))
+ || (other.end() && cmp(end()->value(), other.end()->value())
+ >= (!end()->is_inclusive() && other.end()->is_inclusive()));
+ }
+ // Returns intervals which cover all values covered by this interval but not covered by the other interval.
+ // Ranges are not overlapping and ordered.
+ // Comparator must define a total ordering on T.
+ template
+ std::vector subtract(const wrapping_interval& other, Comparator&& cmp) const {
+ std::vector result;
+ std::list left;
+ std::list right;
+
+ if (is_wrap_around(cmp)) {
+ auto u = unwrap();
+ left.emplace_back(std::move(u.first));
+ left.emplace_back(std::move(u.second));
+ } else {
+ left.push_back(*this);
+ }
+
+ if (other.is_wrap_around(cmp)) {
+ auto u = other.unwrap();
+ right.emplace_back(std::move(u.first));
+ right.emplace_back(std::move(u.second));
+ } else {
+ right.push_back(other);
+ }
+
+ // left and right contain now non-overlapping, ordered intervals
+
+ while (!left.empty() && !right.empty()) {
+ auto& r1 = left.front();
+ auto& r2 = right.front();
+ if (less_than(r2.end_bound(), r1.start_bound(), cmp)) {
+ right.pop_front();
+ } else if (less_than(r1.end_bound(), r2.start_bound(), cmp)) {
+ result.emplace_back(std::move(r1));
+ left.pop_front();
+ } else { // Overlap
+ auto tmp = std::move(r1);
+ left.pop_front();
+ if (!greater_than_or_equal(r2.end_bound(), tmp.end_bound(), cmp)) {
+ left.push_front({bound(r2.end()->value(), !r2.end()->is_inclusive()), tmp.end()});
+ }
+ if (!less_than_or_equal(r2.start_bound(), tmp.start_bound(), cmp)) {
+ left.push_front({tmp.start(), bound(r2.start()->value(), !r2.start()->is_inclusive())});
+ }
+ }
+ }
+
+ boost::copy(left, std::back_inserter(result));
+
+ // TODO: Merge adjacent intervals (optimization)
+ return result;
+ }
+ // split interval in two around a split_point. split_point has to be inside the interval
+ // split_point will belong to first interval
+ // Comparator must define a total ordering on T.
+ template
+ std::pair, wrapping_interval> split(const T& split_point, Comparator&& cmp) const {
+ assert(contains(split_point, std::forward(cmp)));
+ wrapping_interval left(start(), bound(split_point));
+ wrapping_interval right(bound(split_point, false), end());
+ return std::make_pair(std::move(left), std::move(right));
+ }
+ // Create a sub-interval including values greater than the split_point. Returns std::nullopt if
+ // split_point is after the end (but not included in the interval, in case of wraparound intervals)
+ // Comparator must define a total ordering on T.
+ template
+ std::optional> split_after(const T& split_point, Comparator&& cmp) const {
+ if (contains(split_point, std::forward(cmp))
+ && (!end() || cmp(split_point, end()->value()) != 0)) {
+ return wrapping_interval(bound(split_point, false), end());
+ } else if (end() && cmp(split_point, end()->value()) >= 0) {
+ // whether to return std::nullopt or the full interval is not
+ // well-defined for wraparound intervals; we return nullopt
+ // if split_point is after the end.
+ return std::nullopt;
+ } else {
+ return *this;
+ }
+ }
+ template>
+ static std::optional::bound> transform_bound(Bound&& b, Transformer&& transformer) {
+ if (b) {
+ return { { transformer(std::forward(b).value().value()), b->is_inclusive() } };
+ };
+ return {};
+ }
+ // Transforms this interval into a new interval of a different value type
+ // Supplied transformer should transform value of type T (the old type) into value of type U (the new type).
+ template>
+ wrapping_interval transform(Transformer&& transformer) && {
+ return wrapping_interval(transform_bound(std::move(_start), transformer), transform_bound(std::move(_end), transformer), _singular);
+ }
+ template>
+ wrapping_interval transform(Transformer&& transformer) const & {
+ return wrapping_interval(transform_bound(_start, transformer), transform_bound(_end, transformer), _singular);
+ }
+ template
+ bool equal(const wrapping_interval& other, Comparator&& cmp) const {
+ return bool(_start) == bool(other._start)
+ && bool(_end) == bool(other._end)
+ && (!_start || _start->equal(*other._start, cmp))
+ && (!_end || _end->equal(*other._end, cmp))
+ && _singular == other._singular;
+ }
+ bool operator==(const wrapping_interval& other) const {
+ return (_start == other._start) && (_end == other._end) && (_singular == other._singular);
+ }
+
+ template
+ friend std::ostream& operator<<(std::ostream& out, const wrapping_interval& r);
+private:
+ friend class nonwrapping_interval;
+};
+
+template
+std::ostream& operator<<(std::ostream& out, const wrapping_interval& r) {
+ if (r.is_singular()) {
+ return out << "{" << r.start()->value() << "}";
+ }
+
+ if (!r.start()) {
+ out << "(-inf, ";
+ } else {
+ if (r.start()->is_inclusive()) {
+ out << "[";
+ } else {
+ out << "(";
+ }
+ out << r.start()->value() << ", ";
+ }
+
+ if (!r.end()) {
+ out << "+inf)";
+ } else {
+ out << r.end()->value();
+ if (r.end()->is_inclusive()) {
+ out << "]";
+ } else {
+ out << ")";
+ }
+ }
+
+ return out;
+}
+
+// An interval which can have inclusive, exclusive or open-ended bounds on each end.
+// The end bound can never be smaller than the start bound.
+template
+class nonwrapping_interval {
+ template
+ using optional = std::optional;
+public:
+ using bound = interval_bound;
+
+ template
+ using transformed_type = typename wrapping_interval::template transformed_type;
+private:
+ wrapping_interval _interval;
+public:
+ nonwrapping_interval(T value)
+ : _interval(std::move(value))
+ { }
+ nonwrapping_interval() : nonwrapping_interval({}, {}) { }
+ // Can only be called if start <= end. IDL ctor.
+ nonwrapping_interval(optional start, optional end, bool singular = false)
+ : _interval(std::move(start), std::move(end), singular)
+ { }
+ // Can only be called if !r.is_wrap_around().
+ explicit nonwrapping_interval(wrapping_interval&& r)
+ : _interval(std::move(r))
+ { }
+ // Can only be called if !r.is_wrap_around().
+ explicit nonwrapping_interval(const wrapping_interval& r)
+ : _interval(r)
+ { }
+ operator wrapping_interval() const & {
+ return _interval;
+ }
+ operator wrapping_interval() && {
+ return std::move(_interval);
+ }
+
+ // the point is before the interval.
+ // Comparator must define a total ordering on T.
+ template
+ bool before(const T& point, Comparator&& cmp) const {
+ return _interval.before(point, std::forward(cmp));
+ }
+ // the point is after the interval.
+ // Comparator must define a total ordering on T.
+ template
+ bool after(const T& point, Comparator&& cmp) const {
+ return _interval.after(point, std::forward(cmp));
+ }
+ // check if two intervals overlap.
+ // Comparator must define a total ordering on T.
+ template
+ bool overlaps(const nonwrapping_interval& other, Comparator&& cmp) const {
+ // if both this and other have an open start, the two intervals will overlap.
+ if (!start() && !other.start()) {
+ return true;
+ }
+
+ return wrapping_interval::greater_than_or_equal(_interval.end_bound(), other._interval.start_bound(), cmp)
+ && wrapping_interval::greater_than_or_equal(other._interval.end_bound(), _interval.start_bound(), cmp);
+ }
+ static nonwrapping_interval make(bound start, bound end) {
+ return nonwrapping_interval({std::move(start)}, {std::move(end)});
+ }
+ static nonwrapping_interval make_open_ended_both_sides() {
+ return {{}, {}};
+ }
+ static nonwrapping_interval make_singular(T value) {
+ return {std::move(value)};
+ }
+ static nonwrapping_interval make_starting_with(bound b) {
+ return {{std::move(b)}, {}};
+ }
+ static nonwrapping_interval make_ending_with(bound b) {
+ return {{}, {std::move(b)}};
+ }
+ bool is_singular() const {
+ return _interval.is_singular();
+ }
+ bool is_full() const {
+ return _interval.is_full();
+ }
+ const optional& start() const {
+ return _interval.start();
+ }
+ const optional& end() const {
+ return _interval.end();
+ }
+ // the point is inside the interval
+ // Comparator must define a total ordering on T.
+ template
+ bool contains(const T& point, Comparator&& cmp) const {
+ return !before(point, cmp) && !after(point, cmp);
+ }
+ // Returns true iff all values contained by other are also contained by this.
+ // Comparator must define a total ordering on T.
+ template
+ bool contains(const nonwrapping_interval& other, Comparator&& cmp) const {
+ return wrapping_interval::less_than_or_equal(_interval.start_bound(), other._interval.start_bound(), cmp)
+ && wrapping_interval::greater_than_or_equal(_interval.end_bound(), other._interval.end_bound(), cmp);
+ }
+ // Returns intervals which cover all values covered by this interval but not covered by the other interval.
+ // Ranges are not overlapping and ordered.
+ // Comparator must define a total ordering on T.
+ template
+ std::vector subtract(const nonwrapping_interval& other, Comparator&& cmp) const {
+ auto subtracted = _interval.subtract(other._interval, std::forward(cmp));
+ return boost::copy_range>(subtracted | boost::adaptors::transformed([](auto&& r) {
+ return nonwrapping_interval(std::move(r));
+ }));
+ }
+ // split interval in two around a split_point. split_point has to be inside the interval
+ // split_point will belong to first interval
+ // Comparator must define a total ordering on T.
+ template
+ std::pair, nonwrapping_interval> split(const T& split_point, Comparator&& cmp) const {
+ assert(contains(split_point, std::forward(cmp)));
+ nonwrapping_interval left(start(), bound(split_point));
+ nonwrapping_interval right(bound(split_point, false), end());
+ return std::make_pair(std::move(left), std::move(right));
+ }
+ // Create a sub-interval including values greater than the split_point. If split_point is after
+ // the end, returns std::nullopt.
+ template
+ std::optional split_after(const T& split_point, Comparator&& cmp) const {
+ if (end() && cmp(split_point, end()->value()) >= 0) {
+ return std::nullopt;
+ } else if (start() && cmp(split_point, start()->value()) < 0) {
+ return *this;
+ } else {
+ return nonwrapping_interval(interval_bound(split_point, false), end());
+ }
+ }
+ // Creates a new sub-interval which is the intersection of this interval and an interval starting with "start".
+ // If there is no overlap, returns std::nullopt.
+ template
+ std::optional trim_front(std::optional&& start, Comparator&& cmp) const {
+ return intersection(nonwrapping_interval(std::move(start), {}), cmp);
+ }
+ // Transforms this interval into a new interval of a different value type
+ // Supplied transformer should transform value of type T (the old type) into value of type U (the new type).
+ template>
+ nonwrapping_interval transform(Transformer&& transformer) && {
+ return nonwrapping_interval(std::move(_interval).transform(std::forward(transformer)));
+ }
+ template>
+ nonwrapping_interval transform(Transformer&& transformer) const & {
+ return nonwrapping_interval(_interval.transform(std::forward(transformer)));
+ }
+ template
+ bool equal(const nonwrapping_interval& other, Comparator&& cmp) const {
+ return _interval.equal(other._interval, std::forward(cmp));
+ }
+ bool operator==(const nonwrapping_interval& other) const {
+ return _interval == other._interval;
+ }
+ // Takes a vector of possibly overlapping intervals and returns a vector containing
+ // a set of non-overlapping intervals covering the same values.
+ template
+ static std::vector deoverlap(std::vector intervals, Comparator&& cmp) {
+ auto size = intervals.size();
+ if (size <= 1) {
+ return intervals;
+ }
+
+ std::sort(intervals.begin(), intervals.end(), [&](auto&& r1, auto&& r2) {
+ return wrapping_interval::less_than(r1._interval.start_bound(), r2._interval.start_bound(), cmp);
+ });
+
+ std::vector deoverlapped_intervals;
+ deoverlapped_intervals.reserve(size);
+
+ auto&& current = intervals[0];
+ for (auto&& r : intervals | boost::adaptors::sliced(1, intervals.size())) {
+ bool includes_end = wrapping_interval::greater_than_or_equal(r._interval.end_bound(), current._interval.start_bound(), cmp)
+ && wrapping_interval::greater_than_or_equal(current._interval.end_bound(), r._interval.end_bound(), cmp);
+ if (includes_end) {
+ continue; // last.start <= r.start <= r.end <= last.end
+ }
+ bool includes_start = wrapping_interval::greater_than_or_equal(current._interval.end_bound(), r._interval.start_bound(), cmp);
+ if (includes_start) {
+ current = nonwrapping_interval(std::move(current.start()), std::move(r.end()));
+ } else {
+ deoverlapped_intervals.emplace_back(std::move(current));
+ current = std::move(r);
+ }
+ }
+
+ deoverlapped_intervals.emplace_back(std::move(current));
+ return deoverlapped_intervals;
+ }
+
+private:
+ // These private functions optimize the case where a sequence supports the
+ // lower and upper bound operations more efficiently, as is the case with
+ // some boost containers.
+ struct std_ {};
+ struct built_in_ : std_ {};
+
+ template().lower_bound(std::declval(), std::declval()))>
+ typename std::remove_reference::type::const_iterator do_lower_bound(const T& value, Range&& r, LessComparator&& cmp, built_in_) const {
+ return r.lower_bound(value, std::forward(cmp));
+ }
+
+ template().upper_bound(std::declval(), std::declval()))>
+ typename std::remove_reference::type::const_iterator do_upper_bound(const T& value, Range&& r, LessComparator&& cmp, built_in_) const {
+ return r.upper_bound(value, std::forward(cmp));
+ }
+
+ template
+ typename std::remove_reference::type::const_iterator do_lower_bound(const T& value, Range&& r, LessComparator&& cmp, std_) const {
+ return std::lower_bound(r.begin(), r.end(), value, std::forward(cmp));
+ }
+
+ template
+ typename std::remove_reference::type::const_iterator do_upper_bound(const T& value, Range&& r, LessComparator&& cmp, std_) const {
+ return std::upper_bound(r.begin(), r.end(), value, std::forward(cmp));
+ }
+public:
+ // Return the lower bound of the specified sequence according to these bounds.
+ template
+ typename std::remove_reference::type::const_iterator lower_bound(Range&& r, LessComparator&& cmp) const {
+ return start()
+ ? (start()->is_inclusive()
+ ? do_lower_bound(start()->value(), std::forward(r), std::forward(cmp), built_in_())
+ : do_upper_bound(start()->value(), std::forward(r), std::forward(cmp), built_in_()))
+ : std::cbegin(r);
+ }
+ // Return the upper bound of the specified sequence according to these bounds.
+ template
+ typename std::remove_reference::type::const_iterator upper_bound(Range&& r, LessComparator&& cmp) const {
+ return end()
+ ? (end()->is_inclusive()
+ ? do_upper_bound(end()->value(), std::forward(r), std::forward(cmp), built_in_())
+ : do_lower_bound(end()->value(), std::forward(r), std::forward(cmp), built_in_()))
+ : (is_singular()
+ ? do_upper_bound(start()->value(), std::forward(r), std::forward(cmp), built_in_())
+ : std::cend(r));
+ }
+ // Returns a subset of the range that is within these bounds.
+ template
+ boost::iterator_range::type::const_iterator>
+ slice(Range&& range, LessComparator&& cmp) const {
+ return boost::make_iterator_range(lower_bound(range, cmp), upper_bound(range, cmp));
+ }
+
+ // Returns the intersection between this interval and other.
+ template
+ std::optional intersection(const nonwrapping_interval& other, Comparator&& cmp) const {
+ auto p = std::minmax(_interval, other._interval, [&cmp] (auto&& a, auto&& b) {
+ return wrapping_interval::less_than(a.start_bound(), b.start_bound(), cmp);
+ });
+ if (wrapping_interval::greater_than_or_equal(p.first.end_bound(), p.second.start_bound(), cmp)) {
+ auto end = std::min(p.first.end_bound(), p.second.end_bound(), [&cmp] (auto&& a, auto&& b) {
+ return !wrapping_interval::greater_than_or_equal(a, b, cmp);
+ });
+ return nonwrapping_interval(p.second.start(), end.b);
+ }
+ return {};
+ }
+
+ template
+ friend std::ostream& operator<<(std::ostream& out, const nonwrapping_interval& r);
+};
+
+template
+std::ostream& operator<<(std::ostream& out, const nonwrapping_interval& r) {
+ return out << r._interval;
+}
+
+template typename T, typename U>
+concept Interval = std::is_same, wrapping_interval>::value || std::is_same, nonwrapping_interval>::value;
+
+// Allow using interval in a hash table. The hash function 31 * left +
+// right is the same one used by Cassandra's AbstractBounds.hashCode().
+namespace std {
+
+template
+struct hash> {
+ using argument_type = wrapping_interval;
+ using result_type = decltype(std::hash()(std::declval()));
+ result_type operator()(argument_type const& s) const {
+ auto hash = std::hash();
+ auto left = s.start() ? hash(s.start()->value()) : 0;
+ auto right = s.end() ? hash(s.end()->value()) : 0;
+ return 31 * left + right;
+ }
+};
+
+template
+struct hash> {
+ using argument_type = nonwrapping_interval;
+ using result_type = decltype(std::hash()(std::declval()));
+ result_type operator()(argument_type const& s) const {
+ return hash>()(s);
+ }
+};
+
+}
diff --git a/range.hh b/range.hh
index 7e94527735..1f8e2a7167 100644
--- a/range.hh
+++ b/range.hh
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 ScyllaDB
+ * Copyright (C) 2020 ScyllaDB
*/
/*
@@ -21,708 +21,22 @@
#pragma once
-#include
-#include
-#include
-#include
-#include
-#include
-#include
+#include "interval.hh"
-template
-class range_bound {
- T _value;
- bool _inclusive;
-public:
- range_bound(T value, bool inclusive = true)
- : _value(std::move(value))
- , _inclusive(inclusive)
- { }
- const T& value() const & { return _value; }
- T&& value() && { return std::move(_value); }
- bool is_inclusive() const { return _inclusive; }
- bool operator==(const range_bound& other) const {
- return (_value == other._value) && (_inclusive == other._inclusive);
- }
- template
- bool equal(const range_bound& other, Comparator&& cmp) const {
- return _inclusive == other._inclusive && cmp(_value, other._value) == 0;
- }
-};
+// range.hh is deprecated and should be replaced with interval.hh
-template
-class nonwrapping_range;
-// A range which can have inclusive, exclusive or open-ended bounds on each end.
-// The end bound can be smaller than the start bound.
-template
-class wrapping_range {
- template
- using optional = std::optional;
-public:
- using bound = range_bound;
+template
+using range_bound = interval_bound;
- template
- using transformed_type = typename std::remove_cv_t>>;
-private:
- optional _start;
- optional _end;
- bool _singular;
-public:
- wrapping_range(optional start, optional end, bool singular = false)
- : _start(std::move(start))
- , _singular(singular) {
- if (!_singular) {
- _end = std::move(end);
- }
- }
- wrapping_range(T value)
- : _start(bound(std::move(value), true))
- , _end()
- , _singular(true)
- { }
- wrapping_range() : wrapping_range({}, {}) { }
-private:
- // Bound wrappers for compile-time dispatch and safety.
- struct start_bound_ref { const optional& b; };
- struct end_bound_ref { const optional& b; };
+template
+using nonwrapping_range = interval;
- start_bound_ref start_bound() const { return { start() }; }
- end_bound_ref end_bound() const { return { end() }; }
+template
+using wrapping_range = wrapping_interval;
- template
- static bool greater_than_or_equal(end_bound_ref end, start_bound_ref start, Comparator&& cmp) {
- return !end.b || !start.b || cmp(end.b->value(), start.b->value())
- >= (!end.b->is_inclusive() || !start.b->is_inclusive());
- }
+template
+using range = wrapping_interval;
- template
- static bool less_than(end_bound_ref end, start_bound_ref start, Comparator&& cmp) {
- return !greater_than_or_equal(end, start, cmp);
- }
-
- template
- static bool less_than_or_equal(start_bound_ref first, start_bound_ref second, Comparator&& cmp) {
- return !first.b || (second.b && cmp(first.b->value(), second.b->value())
- <= -(!first.b->is_inclusive() && second.b->is_inclusive()));
- }
-
- template
- static bool less_than(start_bound_ref first, start_bound_ref second, Comparator&& cmp) {
- return second.b && (!first.b || cmp(first.b->value(), second.b->value())
- < (first.b->is_inclusive() && !second.b->is_inclusive()));
- }
-
- template
- static bool greater_than_or_equal(end_bound_ref first, end_bound_ref second, Comparator&& cmp) {
- return !first.b || (second.b && cmp(first.b->value(), second.b->value())
- >= (!first.b->is_inclusive() && second.b->is_inclusive()));
- }
-public:
- // the point is before the range (works only for non wrapped ranges)
- // Comparator must define a total ordering on T.
- template
- bool before(const T& point, Comparator&& cmp) const {
- assert(!is_wrap_around(cmp));
- if (!start()) {
- return false; //open start, no points before
- }
- auto r = cmp(point, start()->value());
- if (r < 0) {
- return true;
- }
- if (!start()->is_inclusive() && r == 0) {
- return true;
- }
- return false;
- }
- // the point is after the range (works only for non wrapped ranges)
- // Comparator must define a total ordering on T.
- template
- bool after(const T& point, Comparator&& cmp) const {
- assert(!is_wrap_around(cmp));
- if (!end()) {
- return false; //open end, no points after
- }
- auto r = cmp(end()->value(), point);
- if (r < 0) {
- return true;
- }
- if (!end()->is_inclusive() && r == 0) {
- return true;
- }
- return false;
- }
- // check if two ranges overlap.
- // Comparator must define a total ordering on T.
- template
- bool overlaps(const wrapping_range& other, Comparator&& cmp) const {
- bool this_wraps = is_wrap_around(cmp);
- bool other_wraps = other.is_wrap_around(cmp);
-
- if (this_wraps && other_wraps) {
- return true;
- } else if (this_wraps) {
- auto unwrapped = unwrap();
- return other.overlaps(unwrapped.first, cmp) || other.overlaps(unwrapped.second, cmp);
- } else if (other_wraps) {
- auto unwrapped = other.unwrap();
- return overlaps(unwrapped.first, cmp) || overlaps(unwrapped.second, cmp);
- }
-
- // No range should reach this point as wrap around.
- assert(!this_wraps);
- assert(!other_wraps);
-
- // if both this and other have an open start, the two ranges will overlap.
- if (!start() && !other.start()) {
- return true;
- }
-
- return greater_than_or_equal(end_bound(), other.start_bound(), cmp)
- && greater_than_or_equal(other.end_bound(), start_bound(), cmp);
- }
- static wrapping_range make(bound start, bound end) {
- return wrapping_range({std::move(start)}, {std::move(end)});
- }
- static wrapping_range make_open_ended_both_sides() {
- return {{}, {}};
- }
- static wrapping_range make_singular(T value) {
- return {std::move(value)};
- }
- static wrapping_range make_starting_with(bound b) {
- return {{std::move(b)}, {}};
- }
- static wrapping_range make_ending_with(bound b) {
- return {{}, {std::move(b)}};
- }
- bool is_singular() const {
- return _singular;
- }
- bool is_full() const {
- return !_start && !_end;
- }
- void reverse() {
- if (!_singular) {
- std::swap(_start, _end);
- }
- }
- const optional& start() const {
- return _start;
- }
- const optional& end() const {
- return _singular ? _start : _end;
- }
- // Range is a wrap around if end value is smaller than the start value
- // or they're equal and at least one bound is not inclusive.
- // Comparator must define a total ordering on T.
- template
- bool is_wrap_around(Comparator&& cmp) const {
- if (_end && _start) {
- auto r = cmp(end()->value(), start()->value());
- return r < 0
- || (r == 0 && (!start()->is_inclusive() || !end()->is_inclusive()));
- } else {
- return false; // open ended range or singular range don't wrap around
- }
- }
- // Converts a wrap-around range to two non-wrap-around ranges.
- // The returned ranges are not overlapping and ordered.
- // Call only when is_wrap_around().
- std::pair unwrap() const {
- return {
- { {}, end() },
- { start(), {} }
- };
- }
- // the point is inside the range
- // Comparator must define a total ordering on T.
- template
- bool contains(const T& point, Comparator&& cmp) const {
- if (is_wrap_around(cmp)) {
- auto unwrapped = unwrap();
- return unwrapped.first.contains(point, cmp)
- || unwrapped.second.contains(point, cmp);
- } else {
- return !before(point, cmp) && !after(point, cmp);
- }
- }
- // Returns true iff all values contained by other are also contained by this.
- // Comparator must define a total ordering on T.
- template
- bool contains(const wrapping_range& other, Comparator&& cmp) const {
- bool this_wraps = is_wrap_around(cmp);
- bool other_wraps = other.is_wrap_around(cmp);
-
- if (this_wraps && other_wraps) {
- return cmp(start()->value(), other.start()->value())
- <= -(!start()->is_inclusive() && other.start()->is_inclusive())
- && cmp(end()->value(), other.end()->value())
- >= (!end()->is_inclusive() && other.end()->is_inclusive());
- }
-
- if (!this_wraps && !other_wraps) {
- return less_than_or_equal(start_bound(), other.start_bound(), cmp)
- && greater_than_or_equal(end_bound(), other.end_bound(), cmp);
- }
-
- if (other_wraps) { // && !this_wraps
- return !start() && !end();
- }
-
- // !other_wraps && this_wraps
- return (other.start() && cmp(start()->value(), other.start()->value())
- <= -(!start()->is_inclusive() && other.start()->is_inclusive()))
- || (other.end() && cmp(end()->value(), other.end()->value())
- >= (!end()->is_inclusive() && other.end()->is_inclusive()));
- }
- // Returns ranges which cover all values covered by this range but not covered by the other range.
- // Ranges are not overlapping and ordered.
- // Comparator must define a total ordering on T.
- template
- std::vector subtract(const wrapping_range& other, Comparator&& cmp) const {
- std::vector result;
- std::list left;
- std::list right;
-
- if (is_wrap_around(cmp)) {
- auto u = unwrap();
- left.emplace_back(std::move(u.first));
- left.emplace_back(std::move(u.second));
- } else {
- left.push_back(*this);
- }
-
- if (other.is_wrap_around(cmp)) {
- auto u = other.unwrap();
- right.emplace_back(std::move(u.first));
- right.emplace_back(std::move(u.second));
- } else {
- right.push_back(other);
- }
-
- // left and right contain now non-overlapping, ordered ranges
-
- while (!left.empty() && !right.empty()) {
- auto& r1 = left.front();
- auto& r2 = right.front();
- if (less_than(r2.end_bound(), r1.start_bound(), cmp)) {
- right.pop_front();
- } else if (less_than(r1.end_bound(), r2.start_bound(), cmp)) {
- result.emplace_back(std::move(r1));
- left.pop_front();
- } else { // Overlap
- auto tmp = std::move(r1);
- left.pop_front();
- if (!greater_than_or_equal(r2.end_bound(), tmp.end_bound(), cmp)) {
- left.push_front({bound(r2.end()->value(), !r2.end()->is_inclusive()), tmp.end()});
- }
- if (!less_than_or_equal(r2.start_bound(), tmp.start_bound(), cmp)) {
- left.push_front({tmp.start(), bound(r2.start()->value(), !r2.start()->is_inclusive())});
- }
- }
- }
-
- boost::copy(left, std::back_inserter(result));
-
- // TODO: Merge adjacent ranges (optimization)
- return result;
- }
- // split range in two around a split_point. split_point has to be inside the range
- // split_point will belong to first range
- // Comparator must define a total ordering on T.
- template
- std::pair, wrapping_range> split(const T& split_point, Comparator&& cmp) const {
- assert(contains(split_point, std::forward(cmp)));
- wrapping_range left(start(), bound(split_point));
- wrapping_range right(bound(split_point, false), end());
- return std::make_pair(std::move(left), std::move(right));
- }
- // Create a sub-range including values greater than the split_point. Returns std::nullopt if
- // split_point is after the end (but not included in the range, in case of wraparound ranges)
- // Comparator must define a total ordering on T.
- template
- std::optional> split_after(const T& split_point, Comparator&& cmp) const {
- if (contains(split_point, std::forward(cmp))
- && (!end() || cmp(split_point, end()->value()) != 0)) {
- return wrapping_range(bound(split_point, false), end());
- } else if (end() && cmp(split_point, end()->value()) >= 0) {
- // whether to return std::nullopt or the full range is not
- // well-defined for wraparound ranges; we return nullopt
- // if split_point is after the end.
- return std::nullopt;
- } else {
- return *this;
- }
- }
- template>
- static std::optional::bound> transform_bound(Bound&& b, Transformer&& transformer) {
- if (b) {
- return { { transformer(std::forward(b).value().value()), b->is_inclusive() } };
- };
- return {};
- }
- // Transforms this range into a new range of a different value type
- // Supplied transformer should transform value of type T (the old type) into value of type U (the new type).
- template>
- wrapping_range transform(Transformer&& transformer) && {
- return wrapping_range(transform_bound(std::move(_start), transformer), transform_bound(std::move(_end), transformer), _singular);
- }
- template>
- wrapping_range transform(Transformer&& transformer) const & {
- return wrapping_range(transform_bound(_start, transformer), transform_bound(_end, transformer), _singular);
- }
- template
- bool equal(const wrapping_range& other, Comparator&& cmp) const {
- return bool(_start) == bool(other._start)
- && bool(_end) == bool(other._end)
- && (!_start || _start->equal(*other._start, cmp))
- && (!_end || _end->equal(*other._end, cmp))
- && _singular == other._singular;
- }
- bool operator==(const wrapping_range& other) const {
- return (_start == other._start) && (_end == other._end) && (_singular == other._singular);
- }
-
- template
- friend std::ostream& operator<<(std::ostream& out, const wrapping_range& r);
-private:
- friend class nonwrapping_range;
-};
-
-template
-std::ostream& operator<<(std::ostream& out, const wrapping_range& r) {
- if (r.is_singular()) {
- return out << "{" << r.start()->value() << "}";
- }
-
- if (!r.start()) {
- out << "(-inf, ";
- } else {
- if (r.start()->is_inclusive()) {
- out << "[";
- } else {
- out << "(";
- }
- out << r.start()->value() << ", ";
- }
-
- if (!r.end()) {
- out << "+inf)";
- } else {
- out << r.end()->value();
- if (r.end()->is_inclusive()) {
- out << "]";
- } else {
- out << ")";
- }
- }
-
- return out;
-}
-
-// A range which can have inclusive, exclusive or open-ended bounds on each end.
-// The end bound can never be smaller than the start bound.
-template