Merge 'cql3, test, utils: switch from boost::adaptors::uniqued to utils::views:unique ' from Kefu Chai
In order to reduce the dependency on external libraries, and for better integration with ranges in C++ standard library. let's use the homebrew `utils::views::unique()` before unique is accepted by the C++ standard. --- it's a cleanup, hence no need to backport. Closes scylladb/scylladb#22393 * github.com:scylladb/scylladb: cql3, test: switch from boost::adaptors::uniqued to utils::views:unique utils: implement drop-in replacement for replacing boost::adaptors::uniqued
This commit is contained in:
@@ -571,6 +571,7 @@ scylla_tests = set([
|
||||
'test/boost/wasm_alloc_test',
|
||||
'test/boost/wasm_test',
|
||||
'test/boost/wrapping_interval_test',
|
||||
'test/boost/unique_view_test',
|
||||
'test/manual/ec2_snitch_test',
|
||||
'test/manual/enormous_table_scan_test',
|
||||
'test/manual/gce_snitch_test',
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "service/storage_proxy.hh"
|
||||
#include "tracing/trace_state.hh"
|
||||
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
#include <boost/range/adaptor/uniqued.hpp>
|
||||
#include "utils/unique_view.hh"
|
||||
|
||||
template<typename T = void>
|
||||
using coordinator_result = exceptions::coordinator_result<T>;
|
||||
@@ -128,12 +126,12 @@ void batch_statement::validate()
|
||||
|
||||
if (_has_conditions
|
||||
&& !_statements.empty()
|
||||
&& (boost::distance(_statements
|
||||
| boost::adaptors::transformed([] (auto&& s) { return s.statement->keyspace(); })
|
||||
| boost::adaptors::uniqued) != 1
|
||||
|| (boost::distance(_statements
|
||||
| boost::adaptors::transformed([] (auto&& s) { return s.statement->column_family(); })
|
||||
| boost::adaptors::uniqued) != 1))) {
|
||||
&& (std::ranges::distance(_statements
|
||||
| std::views::transform([] (auto&& s) { return s.statement->keyspace(); })
|
||||
| utils::views::unique) != 1
|
||||
|| (std::ranges::distance(_statements
|
||||
| std::views::transform([] (auto&& s) { return s.statement->column_family(); })
|
||||
| utils::views::unique) != 1))) {
|
||||
throw exceptions::invalid_request_exception("BATCH with conditions cannot span multiple tables");
|
||||
}
|
||||
std::optional<bool> raw_counter;
|
||||
|
||||
@@ -263,6 +263,8 @@ add_scylla_test(transport_test
|
||||
KIND SEASTAR)
|
||||
add_scylla_test(types_test
|
||||
KIND SEASTAR)
|
||||
add_scylla_test(unique_view_test
|
||||
KIND BOOST)
|
||||
add_scylla_test(utf8_test
|
||||
KIND BOOST
|
||||
LIBRARIES utils)
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <boost/range/irange.hpp>
|
||||
#include <boost/range/algorithm.hpp>
|
||||
#include <boost/range/adaptor/uniqued.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
|
||||
@@ -26,6 +25,7 @@
|
||||
#include "transport/messages/result_message.hh"
|
||||
#include "utils/assert.hh"
|
||||
#include "utils/big_decimal.hh"
|
||||
#include "utils/unique_view.hh"
|
||||
#include "types/map.hh"
|
||||
#include "types/list.hh"
|
||||
#include "types/set.hh"
|
||||
@@ -75,7 +75,7 @@ SEASTAR_TEST_CASE(test_functions) {
|
||||
msg->accept(v);
|
||||
// No boost::adaptors::sorted
|
||||
std::ranges::sort(v.res);
|
||||
BOOST_REQUIRE_EQUAL(boost::distance(v.res | boost::adaptors::uniqued), 3);
|
||||
BOOST_REQUIRE_EQUAL(std::ranges::distance(v.res | utils::views::unique), 3);
|
||||
}).then([&] {
|
||||
return e.execute_cql("select sum(c1), count(c1) from cf where p1 = 'key1';");
|
||||
}).then([] (shared_ptr<cql_transport::messages::result_message> msg) {
|
||||
|
||||
116
test/boost/unique_view_test.cc
Normal file
116
test/boost/unique_view_test.cc
Normal file
@@ -0,0 +1,116 @@
|
||||
#define BOOST_TEST_MODULE test-ranges
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <ranges>
|
||||
|
||||
#include "utils/unique_view.hh"
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_empty_range) {
|
||||
std::vector<int> empty;
|
||||
auto view = empty | utils::views::unique;
|
||||
|
||||
BOOST_CHECK(std::ranges::empty(view));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_single_element) {
|
||||
std::vector<int> single{42};
|
||||
auto view = single | utils::views::unique;
|
||||
|
||||
BOOST_CHECK_EQUAL(std::ranges::distance(view), 1);
|
||||
BOOST_CHECK_EQUAL(*view.begin(), 42);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_all_same_elements) {
|
||||
std::vector<int> same{1, 1, 1, 1, 1};
|
||||
auto view = same | utils::views::unique;
|
||||
|
||||
BOOST_CHECK_EQUAL(std::ranges::distance(view), 1);
|
||||
BOOST_CHECK_EQUAL(*view.begin(), 1);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_all_different_elements) {
|
||||
std::vector<int> different{1, 2, 3, 4, 5};
|
||||
auto result = different | utils::views::unique | std::ranges::to<std::vector>();
|
||||
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(),
|
||||
different.begin(), different.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_consecutive_duplicates) {
|
||||
std::vector<int> input{1, 1, 2, 2, 3, 3, 2, 2, 1, 1};
|
||||
auto result = input | utils::views::unique | std::ranges::to<std::vector>();
|
||||
|
||||
std::vector<int> expected{1, 2, 3, 2, 1};
|
||||
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(),
|
||||
expected.begin(), expected.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_string_elements) {
|
||||
std::vector<std::string> input{"hello", "hello", "world", "world", "hello"};
|
||||
auto result = input | utils::views::unique | std::ranges::to<std::vector>();
|
||||
|
||||
std::vector<std::string> expected{"hello", "world", "hello"};
|
||||
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(),
|
||||
expected.begin(), expected.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_different_container_type) {
|
||||
std::list<int> input{1, 1, 2, 2, 3, 3};
|
||||
auto result = input | utils::views::unique | std::ranges::to<std::vector>();
|
||||
|
||||
std::vector<int> expected{1, 2, 3};
|
||||
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(),
|
||||
expected.begin(), expected.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_const_range) {
|
||||
const std::vector<int> input{1, 1, 2, 2, 3};
|
||||
auto result = input | utils::views::unique | std::ranges::to<std::vector>();
|
||||
|
||||
std::vector<int> expected{1, 2, 3};
|
||||
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(),
|
||||
expected.begin(), expected.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_non_common_range) {
|
||||
auto result = std::views::iota(1)
|
||||
| utils::views::unique
|
||||
| std::views::take(3)
|
||||
| std::ranges::to<std::vector>();
|
||||
|
||||
std::vector<int> expected{1, 2, 3};
|
||||
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(),
|
||||
expected.begin(), expected.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_composition_with_other_views) {
|
||||
std::vector<int> input{1, 1, 2, 2, 3, 3, 4, 4, 5, 5};
|
||||
auto result = input
|
||||
| utils::views::unique
|
||||
| std::views::take(3)
|
||||
| std::ranges::to<std::vector>();
|
||||
|
||||
std::vector<int> expected{1, 2, 3};
|
||||
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(),
|
||||
expected.begin(), expected.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_function_call_syntax) {
|
||||
std::vector<int> input{1, 1, 2, 2, 3};
|
||||
auto result = utils::views::unique(input) | std::ranges::to<std::vector>();
|
||||
|
||||
std::vector<int> expected{1, 2, 3};
|
||||
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(),
|
||||
expected.begin(), expected.end());
|
||||
}
|
||||
201
utils/unique_view.hh
Normal file
201
utils/unique_view.hh
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) 2025-present ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <iterator>
|
||||
#include <ranges>
|
||||
#include <type_traits>
|
||||
|
||||
/**
|
||||
* @brief A lazy view adapter that yields only the first element from every consecutive
|
||||
* group of equal elements in the underlying range.
|
||||
*
|
||||
* This view adapter provides similar functionality to boost::adaptors::uniqued but
|
||||
* is compatible with C++20 ranges.
|
||||
*
|
||||
* @section implementations Related Implementations
|
||||
*
|
||||
* @subsection this_impl This implementation (unique_view)
|
||||
* - Creates a non-modifying view over the source range
|
||||
* - Lazily filters consecutive duplicates
|
||||
* - Compatible with C++20 ranges (satisfies view/range concepts)
|
||||
* - Can be composed with other range adaptors
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* range | unique_view{} | std::views::take(n)
|
||||
* @endcode
|
||||
*
|
||||
* @subsection boost_uniqued boost::adaptors::uniqued
|
||||
* - Functionally identical to this implementation
|
||||
* - Not compatible with C++20 ranges (doesn't satisfy required concepts)
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* range | boost::adaptors::uniqued
|
||||
* @endcode
|
||||
*
|
||||
* @subsection ranges_unique std::ranges::unique (C++23)
|
||||
* - Eager algorithm that modifies the source range in-place
|
||||
* - Returns iterator to new end after removing duplicates
|
||||
* - Cannot be used as a view or composed with other adaptors
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* auto r = std::ranges::unique(range); // range is modified
|
||||
* @endcode
|
||||
*
|
||||
* @subsection std_unique std::unique (pre-C++20)
|
||||
* - Like std::ranges::unique but with iterator-based interface
|
||||
* - Modifies source range in-place
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* auto it = std::unique(range.begin(), range.end());
|
||||
* @endcode
|
||||
*
|
||||
* @subsection boost_range_unique boost::range::unique
|
||||
* - Range-based wrapper around std::unique
|
||||
* - Modifies source range in-place
|
||||
*
|
||||
* @section future Future Standardization
|
||||
* std::views::unique is proposed for C++26 in P2214 and will provide
|
||||
* standardized lazy unique view functionality with expected API:
|
||||
* @code
|
||||
* range | std::views::unique
|
||||
* @endcode
|
||||
*
|
||||
* @section why Why This Implementation
|
||||
* 1. std::ranges::unique/std::unique modify the source range, whereas we need
|
||||
* a non-destructive view
|
||||
* 2. boost::adaptors::uniqued is incompatible with C++20 ranges
|
||||
* 3. std::views::unique isn't standardized yet (targeting C++26)
|
||||
*
|
||||
* @section compat API Compatibility
|
||||
* - Provides pipe operator (|) for consistency with range adaptor patterns
|
||||
* - Can be used as drop-in replacement for boost::adaptors::uniqued in most cases
|
||||
* - Satisfies C++20 view/range concepts for compatibility with std::ranges
|
||||
*
|
||||
* @section usage Usage Example
|
||||
* @code
|
||||
* std::vector<int> v{1, 1, 2, 2, 3, 3};
|
||||
* auto unique = v | unique_view{}; // yields: 1, 2, 3
|
||||
* // v is unchanged, unique is a view
|
||||
* @endcode
|
||||
*/
|
||||
namespace utils {
|
||||
|
||||
template <std::ranges::input_range V>
|
||||
requires std::ranges::view<V> && std::equality_comparable<std::ranges::range_reference_t<V>>
|
||||
class unique_view : public std::ranges::view_interface<unique_view<V>> {
|
||||
V _base = V();
|
||||
|
||||
class iterator;
|
||||
class sentinel {
|
||||
std::ranges::sentinel_t<V> _end = std::ranges::sentinel_t<V>();
|
||||
|
||||
public:
|
||||
sentinel() = default;
|
||||
|
||||
constexpr explicit sentinel(unique_view* parent)
|
||||
: _end(std::ranges::end(parent->_base)) {}
|
||||
|
||||
friend constexpr bool operator==(const iterator& it, const sentinel& sent) {
|
||||
return it._current == sent._end;
|
||||
}
|
||||
};
|
||||
|
||||
class iterator {
|
||||
friend class sentinel;
|
||||
using base_iterator = std::ranges::iterator_t<V>;
|
||||
base_iterator _current = base_iterator();
|
||||
base_iterator _next = base_iterator();
|
||||
std::ranges::sentinel_t<V> _end = std::ranges::sentinel_t<V>();
|
||||
public:
|
||||
using iterator_concept = std::forward_iterator_tag;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = std::ranges::range_value_t<V>;
|
||||
using difference_type = std::ranges::range_difference_t<V>;
|
||||
|
||||
iterator() requires std::default_initializable<base_iterator> = default;
|
||||
|
||||
constexpr iterator(base_iterator current, std::ranges::sentinel_t<V> end)
|
||||
: _current(current)
|
||||
, _next(current)
|
||||
, _end(end) {
|
||||
if (_current != _end) {
|
||||
_next = std::next(_current);
|
||||
skip_duplicates();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr const std::ranges::range_reference_t<V> operator*() const {
|
||||
return *_current;
|
||||
}
|
||||
|
||||
constexpr iterator& operator++() {
|
||||
_current = _next;
|
||||
if (_current != _end) {
|
||||
_next = std::next(_current);
|
||||
skip_duplicates();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr iterator operator++(int) {
|
||||
auto tmp = *this;
|
||||
++*this;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
friend constexpr bool operator==(const iterator& x, const iterator& y)
|
||||
requires std::equality_comparable<base_iterator> {
|
||||
return x._current == y._current;
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr void skip_duplicates() {
|
||||
while (_next != _end && *_current == *_next) {
|
||||
++_next;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
unique_view() requires std::default_initializable<V> = default;
|
||||
|
||||
constexpr explicit unique_view(V base)
|
||||
: _base(std::move(base)) {}
|
||||
|
||||
constexpr auto begin() {
|
||||
return iterator{std::ranges::begin(_base), std::ranges::end(_base)};
|
||||
}
|
||||
|
||||
constexpr auto end() {
|
||||
return sentinel{this};
|
||||
}
|
||||
};
|
||||
|
||||
template<class R>
|
||||
unique_view(R&&) -> unique_view<std::views::all_t<R>>;
|
||||
|
||||
namespace detail {
|
||||
struct unique_closure : std::ranges::range_adaptor_closure<unique_closure> {
|
||||
template<std::ranges::viewable_range R>
|
||||
constexpr auto operator()(R&& r) const {
|
||||
return unique_view(std::forward<R>(r));
|
||||
}
|
||||
};
|
||||
}
|
||||
namespace views {
|
||||
inline constexpr detail::unique_closure unique{};
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
Reference in New Issue
Block a user