mirror of
https://github.com/scylladb/scylladb.git
synced 2026-05-30 03:30:49 +00:00
If the key type is int64_t and the less-comparator is "natural" (i.e. it's literally 'a < b') we may use the SIMD instructions to search for the key on a node. Before doing so, the maybe_key and the searcher should be prepared for that, in particular: 1. maybe_key should set unused keys to the minimal value 2. the searcher for this case should call the gt() helper with primitive types -- int64_t search key and array of int64_t values To tell to B+ code that the key-less pair is such the less-er should define the simplify_key() method converting search keys to int64_t-s. This searcher is selected automatically, if any mismatch happens it silently falls back to default one. Thus also add a static assertion to the row-cache to mitigate this. Signed-off-by: Pavel Emelyanov <xemul@scylladb.com>
371 lines
9.7 KiB
C++
371 lines
9.7 KiB
C++
|
|
/*
|
|
* Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define BOOST_TEST_MODULE bptree
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
#include <fmt/core.h>
|
|
|
|
#include "utils/bptree.hh"
|
|
#include "test/unit/tree_test_key.hh"
|
|
|
|
struct int_compare {
|
|
bool operator()(const int& a, const int& b) const noexcept { return a < b; }
|
|
};
|
|
|
|
using namespace bplus;
|
|
using test_key = tree_test_key_base;
|
|
using test_tree = tree<int, unsigned long, int_compare, 4, key_search::both, with_debug::yes>;
|
|
|
|
BOOST_AUTO_TEST_CASE(test_ops_empty_tree) {
|
|
/* Sanity checks for no nullptr dereferences */
|
|
test_tree t(int_compare{});
|
|
t.erase(1);
|
|
t.find(1);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_double_insert) {
|
|
/* No assertions should happen in ~tree */
|
|
test_tree t(int_compare{});
|
|
auto i = t.emplace(1, 1);
|
|
BOOST_REQUIRE(i.second);
|
|
i = t.emplace(1, 1);
|
|
BOOST_REQUIRE(!i.second);
|
|
t.erase(1);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_cookie_find) {
|
|
struct int_to_key_compare {
|
|
bool operator()(const test_key& a, const int& b) const noexcept { return (int)a < b; }
|
|
bool operator()(const int& a, const test_key& b) const noexcept { return a < (int)b; }
|
|
bool operator()(const test_key& a, const test_key& b) const noexcept {
|
|
test_key_compare cmp;
|
|
return cmp(a, b);
|
|
}
|
|
};
|
|
|
|
using test_tree = tree<test_key, int, int_to_key_compare, 4, key_search::both, with_debug::yes>;
|
|
|
|
test_tree t(int_to_key_compare{});
|
|
t.emplace(test_key{1}, 132);
|
|
|
|
auto i = t.find(1);
|
|
BOOST_REQUIRE(*i == 132);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_double_erase) {
|
|
test_tree t(int_compare{});
|
|
t.emplace(1, 1);
|
|
t.emplace(2, 2);
|
|
auto i = t.erase(1);
|
|
BOOST_REQUIRE(*i == 2);
|
|
i = t.erase(1);
|
|
BOOST_REQUIRE(i == t.end());
|
|
i = t.erase(2);
|
|
BOOST_REQUIRE(i == t.end());
|
|
t.erase(2);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_remove_corner_case) {
|
|
/* Sanity check for erasure to be precise */
|
|
test_tree t(int_compare{});
|
|
t.emplace(1, 1);
|
|
t.emplace(2, 123);
|
|
t.emplace(3, 3);
|
|
t.erase(1);
|
|
t.erase(3);
|
|
auto f = t.find(2);
|
|
BOOST_REQUIRE(*f == 123);
|
|
t.erase(2);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_end_iterator) {
|
|
/* Check std::prev(end()) */
|
|
test_tree t(int_compare{});
|
|
t.emplace(1, 123);
|
|
auto i = std::prev(t.end());
|
|
BOOST_REQUIRE(*i = 123);
|
|
t.erase(1);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_next_to_end_iterator) {
|
|
/* Same, but with "artificial" end iterator */
|
|
test_tree t(int_compare{});
|
|
auto i = t.emplace(1, 123).first;
|
|
i++;
|
|
BOOST_REQUIRE(i == t.end());
|
|
i--;
|
|
BOOST_REQUIRE(*i = 123);
|
|
t.erase(1);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_clear) {
|
|
/* Quick check for tree::clear */
|
|
test_tree t(int_compare{});
|
|
|
|
for (int i = 0; i < 32; i++) {
|
|
t.emplace(i, i);
|
|
}
|
|
|
|
t.clear();
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_post_clear) {
|
|
/* Check that tree is work-able after clear */
|
|
test_tree t(int_compare{});
|
|
|
|
t.emplace(1, 1);
|
|
t.clear();
|
|
t.emplace(2, 2);
|
|
t.erase(2);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_iterator_erase) {
|
|
/* Check iterator::erase */
|
|
test_tree t(int_compare{});
|
|
auto it = t.emplace(2, 2);
|
|
t.emplace(1, 321);
|
|
it.first.erase(int_compare{});
|
|
BOOST_REQUIRE(*t.find(1) == 321);
|
|
t.erase(1);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_iterator_equal) {
|
|
test_tree t(int_compare{});
|
|
auto i1 = t.emplace(1, 1);
|
|
auto i2 = t.emplace(2, 2);
|
|
auto i3 = t.find(1);
|
|
BOOST_REQUIRE(i1.first == i3);
|
|
BOOST_REQUIRE(i1.first != i2.first);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_lower_bound) {
|
|
test_tree t(int_compare{});
|
|
t.emplace(1, 11);
|
|
t.emplace(3, 13);
|
|
|
|
bool match;
|
|
BOOST_REQUIRE(*t.lower_bound(0, match) == 11 && !match);
|
|
BOOST_REQUIRE(*t.lower_bound(1, match) == 11 && match);
|
|
BOOST_REQUIRE(*t.lower_bound(2, match) == 13 && !match);
|
|
BOOST_REQUIRE(*t.lower_bound(3, match) == 13 && match);
|
|
BOOST_REQUIRE(t.lower_bound(4, match) == t.end() && !match);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_upper_bound) {
|
|
test_tree t(int_compare{});
|
|
t.emplace(1, 11);
|
|
t.emplace(3, 13);
|
|
|
|
BOOST_REQUIRE(*t.upper_bound(0) == 11);
|
|
BOOST_REQUIRE(*t.upper_bound(1) == 13);
|
|
BOOST_REQUIRE(*t.upper_bound(2) == 13);
|
|
BOOST_REQUIRE(t.upper_bound(3) == t.end());
|
|
BOOST_REQUIRE(t.upper_bound(4) == t.end());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_insert_iterator_index) {
|
|
/* Check insertion iterator ++ and duplicate key */
|
|
test_tree t(int_compare{});
|
|
t.emplace(1, 10);
|
|
t.emplace(3, 13);
|
|
auto i = t.emplace(2, 2).first;
|
|
i++;
|
|
BOOST_REQUIRE(*i == 13);
|
|
auto i2 = t.emplace(2, 2); /* 2nd insert finds the previous */
|
|
BOOST_REQUIRE(!i2.second);
|
|
i2.first++;
|
|
BOOST_REQUIRE(*(i2.first) == 13);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_insert_before) {
|
|
/* Check iterator::insert_before */
|
|
test_tree t(int_compare{});
|
|
auto i3 = t.emplace(3, 13).first;
|
|
auto i2 = i3.emplace_before(2, int_compare{}, 12);
|
|
BOOST_REQUIRE(++i2 == i3);
|
|
BOOST_REQUIRE(*i3 == 13);
|
|
BOOST_REQUIRE(*--i2 == 12);
|
|
BOOST_REQUIRE(*--i3 == 12);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_insert_before_end) {
|
|
/* The same but for end() iterator */
|
|
test_tree t(int_compare{});
|
|
auto i = t.emplace(1, 1).first;
|
|
auto i2 = t.end().emplace_before(2, int_compare{}, 12);
|
|
BOOST_REQUIRE(++i == i2);
|
|
BOOST_REQUIRE(++i2 == t.end());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_insert_before_end_empty) {
|
|
/* The same, but for empty tree */
|
|
test_tree t(int_compare{});
|
|
auto i = t.end().emplace_before(42, int_compare{}, 142);
|
|
BOOST_REQUIRE(i == t.begin());
|
|
t.erase(42);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_iterators) {
|
|
test_tree t(int_compare{});
|
|
|
|
for (auto i = t.rbegin(); i != t.rend(); i++) {
|
|
BOOST_REQUIRE(false);
|
|
}
|
|
for (auto i = t.begin(); i != t.end(); i++) {
|
|
BOOST_REQUIRE(false);
|
|
}
|
|
|
|
t.emplace(1, 7);
|
|
t.emplace(2, 9);
|
|
|
|
{
|
|
auto i = t.begin();
|
|
BOOST_REQUIRE(*(i++) == 7);
|
|
BOOST_REQUIRE(*(i++) == 9);
|
|
BOOST_REQUIRE(i == t.end());
|
|
}
|
|
|
|
{
|
|
auto i = t.rbegin();
|
|
BOOST_REQUIRE(*(i++) == 9);
|
|
BOOST_REQUIRE(*(i++) == 7);
|
|
BOOST_REQUIRE(i == t.rend());
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Special test that makes sure "self-iterator" works OK.
|
|
* See comment near the bptree::iterator(T* d) constructor
|
|
* for details.
|
|
*/
|
|
class tree_data {
|
|
int _key;
|
|
int _cookie;
|
|
public:
|
|
explicit tree_data(int cookie) : _key(-1), _cookie(cookie) {}
|
|
tree_data(int key, int cookie) : _key(key), _cookie(cookie) {}
|
|
int cookie() const { return _cookie; }
|
|
int key() const {
|
|
assert(_key != -1);
|
|
return _key;
|
|
}
|
|
};
|
|
|
|
BOOST_AUTO_TEST_CASE(test_data_self_iterator) {
|
|
using test_tree = tree<int, tree_data, int_compare, 4, key_search::both, with_debug::yes>;
|
|
|
|
test_tree t(int_compare{});
|
|
auto i = t.emplace(1, 42);
|
|
BOOST_REQUIRE(i.second);
|
|
|
|
tree_data* d = &(*i.first);
|
|
BOOST_REQUIRE(d->cookie() == 42);
|
|
|
|
test_tree::iterator di(d);
|
|
BOOST_REQUIRE(di->cookie() == 42);
|
|
|
|
di.erase(int_compare{});
|
|
BOOST_REQUIRE(t.find(1) == t.end());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_insert_before_nokey) {
|
|
using test_tree = tree<int, tree_data, int_compare, 4, key_search::both, with_debug::yes>;
|
|
|
|
test_tree t(int_compare{});
|
|
auto i = t.emplace(2, 52).first;
|
|
auto ni = i.emplace_before(int_compare{}, 1, 42);
|
|
BOOST_REQUIRE(ni->cookie() == 42);
|
|
ni++;
|
|
BOOST_REQUIRE(ni == i);
|
|
}
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE(test_self_iterator_rover) {
|
|
test_tree t(int_compare{});
|
|
auto i = t.emplace(2, 42).first;
|
|
unsigned long* d = &(*i);
|
|
test_tree::iterator di(d);
|
|
|
|
i = di.emplace_before(1, int_compare{}, 31);
|
|
BOOST_REQUIRE(*i == 31);
|
|
BOOST_REQUIRE(*(++i) == 42);
|
|
BOOST_REQUIRE(++i == t.end());
|
|
BOOST_REQUIRE(++di == t.end());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_erase_range) {
|
|
/* Quick check for tree::erase(from, to) */
|
|
test_tree t(int_compare{});
|
|
|
|
for (int i = 0; i < 32; i++) {
|
|
t.emplace(i, i);
|
|
}
|
|
|
|
auto b = t.find(8);
|
|
auto e = t.find(25);
|
|
t.erase(b, e);
|
|
|
|
BOOST_REQUIRE(*t.find(7) == 7);
|
|
BOOST_REQUIRE(t.find(8) == t.end());
|
|
BOOST_REQUIRE(t.find(24) == t.end());
|
|
BOOST_REQUIRE(*t.find(25) == 25);
|
|
}
|
|
|
|
static bool key_simplified = false;
|
|
|
|
static void check_conversions() {
|
|
BOOST_REQUIRE(key_simplified);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_avx_search) {
|
|
struct int64_compare {
|
|
bool operator()(const int64_t& a, const int64_t& b) const noexcept { return a < b; }
|
|
int64_t simplify_key(int64_t k) const noexcept {
|
|
key_simplified = true;
|
|
return k;
|
|
}
|
|
};
|
|
|
|
using test_tree = tree<int64_t, unsigned long, int64_compare, 4, key_search::linear>;
|
|
|
|
test_tree t(int64_compare{});
|
|
|
|
t.emplace(std::numeric_limits<int64_t>::min() + 1, 321);
|
|
t.emplace(0, 213);
|
|
t.emplace(std::numeric_limits<int64_t>::max(), 123);
|
|
|
|
auto i = t.find(std::numeric_limits<int64_t>::min() + 1);
|
|
BOOST_REQUIRE(*i++ == 321);
|
|
BOOST_REQUIRE(*i++ == 213);
|
|
BOOST_REQUIRE(*i++ == 123);
|
|
BOOST_REQUIRE(i == t.end());
|
|
|
|
i = t.find(0);
|
|
BOOST_REQUIRE(*i == 213);
|
|
i = t.find(std::numeric_limits<int64_t>::max());
|
|
BOOST_REQUIRE(*i == 123);
|
|
|
|
t.clear();
|
|
check_conversions();
|
|
}
|