mirror of
https://github.com/scylladb/scylladb.git
synced 2026-04-22 17:40:34 +00:00
This series mainly fixes issues with the serialization of promoted
index entries for non-compound schemas and with the serialization of
range tombstones, also for non-compound schemas.
We lift the correct cell name writing code into its own function,
and direct all users to it. We also ensure backward compatibility with
incorrectly generated promoted indexes and range tombstones.
Fixes #2995
Fixes #2986
Fixes #2979
Fixes #2992
Fixes #2993
* git@github.com:duarten/scylla.git promoted-index-serialization/v3:
sstables/sstables: Unify column name writers
sstables/sstables: Don't write index entry for a missing row maker
sstables/sstables: Reuse write_range_tombstone() for row tombstones
sstables/sstables: Lift index writing for row tombstones
sstables/sstables: Leverage index code upon range tombstone consume
sstables/sstables: Move out tombstone check in write_range_tombstone()
sstables/sstables: A schema with static columns is always compound
sstables/sstables: Lift column name writing logic
sstables/sstables: Use schema-aware write_column_name() for
collections
sstables/sstables: Use schema-aware write_column_name() for row marker
sstables/sstables: Use schema-aware write_column_name() for static row
sstables/sstables: Writing promoted index entry leverages
column_name_writer
sstables/sstables: Add supported feature list to sstables
sstables/sstables: Don't use incorrectly serialized promoted index
cql3/single_column_primary_key_restrictions: Implement is_inclusive()
cql3/delete_statement: Constrain range deletions for non-compound
schemas
tests/cql_query_test: Verify range deletion constraints
sstables/sstables: Correctly deserialize range tombstones
service/storage_service: Add feature for correct non-compound RTs
tests/sstable_*: Start the storage service for some cases
sstables/sstable_writer: Prepare to control range tombstone
serialization
sstables/sstables: Correctly serialize range tombstones
tests/sstable_assertions: Fix monotonicity check for promoted indexes
tests/sstable_assertions: Assert a promoted index is empty
tests/sstable_mutation_test: Verify promoted index serializes
correctly
tests/sstable_mutation_test: Verify promoted index repeats tombstones
tests/sstable_mutation_test: Ensure range tombstone serializes
correctly
tests/sstable_datafile_test: Add test for incorrect promoted index
tests/sstable_datafile_test: Verify reading of incorrect range
tombstones
sstables/sstable: Rename schema-oblivious write_column_name() function
sstables/sstables: No promoted index without clustering keys
tests/sstable_mutation_test: Verify promoted index is not generated
sstables/sstables: Optimize column name writing and indexing
compound_compat: Don't assume compoundness
(cherry picked from commit bd1efbc25c)
Also added sstables::make_sstable() to preserve source compatibility in tests.
339 lines
13 KiB
C++
339 lines
13 KiB
C++
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define BOOST_TEST_MODULE core
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
#include "compound.hh"
|
|
#include "compound_compat.hh"
|
|
#include "tests/range_assert.hh"
|
|
#include "schema_builder.hh"
|
|
|
|
#include "disk-error-handler.hh"
|
|
|
|
thread_local disk_error_signal_type commit_error;
|
|
thread_local disk_error_signal_type general_disk_error;
|
|
|
|
static std::vector<bytes> to_bytes_vec(std::vector<sstring> values) {
|
|
std::vector<bytes> result;
|
|
for (auto&& v : values) {
|
|
result.emplace_back(to_bytes(v));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template <typename Compound>
|
|
static
|
|
range_assert<typename Compound::iterator>
|
|
assert_that_components(Compound& t, bytes packed) {
|
|
return assert_that_range(t.begin(packed), t.end(packed));
|
|
}
|
|
|
|
template <typename Compound>
|
|
static void test_sequence(Compound& t, std::vector<sstring> strings) {
|
|
auto packed = t.serialize_value(to_bytes_vec(strings));
|
|
assert_that_components(t, packed).equals(to_bytes_vec(strings));
|
|
};
|
|
|
|
BOOST_AUTO_TEST_CASE(test_iteration_over_non_prefixable_tuple) {
|
|
compound_type<allow_prefixes::no> t({bytes_type, bytes_type, bytes_type});
|
|
|
|
test_sequence(t, {"el1", "el2", "el3"});
|
|
test_sequence(t, {"el1", "el2", ""});
|
|
test_sequence(t, {"", "el2", "el3"});
|
|
test_sequence(t, {"el1", "", ""});
|
|
test_sequence(t, {"", "", "el3"});
|
|
test_sequence(t, {"el1", "", "el3"});
|
|
test_sequence(t, {"", "", ""});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_iteration_over_prefixable_tuple) {
|
|
compound_type<allow_prefixes::yes> t({bytes_type, bytes_type, bytes_type});
|
|
|
|
test_sequence(t, {"el1", "el2", "el3"});
|
|
test_sequence(t, {"el1", "el2", ""});
|
|
test_sequence(t, {"", "el2", "el3"});
|
|
test_sequence(t, {"el1", "", ""});
|
|
test_sequence(t, {"", "", "el3"});
|
|
test_sequence(t, {"el1", "", "el3"});
|
|
test_sequence(t, {"", "", ""});
|
|
|
|
test_sequence(t, {"el1", "el2", ""});
|
|
test_sequence(t, {"el1", "el2"});
|
|
test_sequence(t, {"el1", ""});
|
|
test_sequence(t, {"el1"});
|
|
test_sequence(t, {""});
|
|
test_sequence(t, {});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_iteration_over_non_prefixable_singular_tuple) {
|
|
compound_type<allow_prefixes::no> t({bytes_type});
|
|
|
|
test_sequence(t, {"el1"});
|
|
test_sequence(t, {""});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_iteration_over_prefixable_singular_tuple) {
|
|
compound_type<allow_prefixes::yes> t({bytes_type});
|
|
|
|
test_sequence(t, {"elem1"});
|
|
test_sequence(t, {""});
|
|
test_sequence(t, {});
|
|
}
|
|
|
|
template <allow_prefixes AllowPrefixes>
|
|
void do_test_conversion_methods_for_singular_compound() {
|
|
compound_type<AllowPrefixes> t({bytes_type});
|
|
|
|
{
|
|
assert_that_components(t, t.serialize_value(to_bytes_vec({"asd"}))) // r-value version
|
|
.equals(to_bytes_vec({"asd"}));
|
|
}
|
|
|
|
{
|
|
auto vec = to_bytes_vec({"asd"});
|
|
assert_that_components(t, t.serialize_value(vec)) // l-value version
|
|
.equals(to_bytes_vec({"asd"}));
|
|
}
|
|
|
|
{
|
|
std::vector<bytes_opt> vec = { bytes_opt("asd") };
|
|
assert_that_components(t, t.serialize_optionals(vec))
|
|
.equals(to_bytes_vec({"asd"}));
|
|
}
|
|
|
|
{
|
|
std::vector<bytes_opt> vec = { bytes_opt("asd") };
|
|
assert_that_components(t, t.serialize_optionals(std::move(vec))) // r-value
|
|
.equals(to_bytes_vec({"asd"}));
|
|
}
|
|
|
|
{
|
|
assert_that_components(t, t.serialize_single(bytes("asd")))
|
|
.equals(to_bytes_vec({"asd"}));
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_conversion_methods_for_singular_compound) {
|
|
do_test_conversion_methods_for_singular_compound<allow_prefixes::yes>();
|
|
do_test_conversion_methods_for_singular_compound<allow_prefixes::no>();
|
|
}
|
|
|
|
template <allow_prefixes AllowPrefixes>
|
|
void do_test_conversion_methods_for_non_singular_compound() {
|
|
compound_type<AllowPrefixes> t({bytes_type, bytes_type, bytes_type});
|
|
|
|
{
|
|
assert_that_components(t, t.serialize_value(to_bytes_vec({"el1", "el2", "el2"}))) // r-value version
|
|
.equals(to_bytes_vec({"el1", "el2", "el2"}));
|
|
}
|
|
|
|
{
|
|
auto vec = to_bytes_vec({"el1", "el2", "el3"});
|
|
assert_that_components(t, t.serialize_value(vec)) // l-value version
|
|
.equals(to_bytes_vec({"el1", "el2", "el3"}));
|
|
}
|
|
|
|
{
|
|
std::vector<bytes_opt> vec = { bytes_opt("el1"), bytes_opt("el2"), bytes_opt("el3") };
|
|
assert_that_components(t, t.serialize_optionals(vec))
|
|
.equals(to_bytes_vec({"el1", "el2", "el3"}));
|
|
}
|
|
|
|
{
|
|
std::vector<bytes_opt> vec = { bytes_opt("el1"), bytes_opt("el2"), bytes_opt("el3") };
|
|
assert_that_components(t, t.serialize_optionals(std::move(vec))) // r-value
|
|
.equals(to_bytes_vec({"el1", "el2", "el3"}));
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_conversion_methods_for_non_singular_compound) {
|
|
do_test_conversion_methods_for_non_singular_compound<allow_prefixes::yes>();
|
|
do_test_conversion_methods_for_non_singular_compound<allow_prefixes::no>();
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_component_iterator_post_incrementation) {
|
|
compound_type<allow_prefixes::no> t({bytes_type, bytes_type, bytes_type});
|
|
|
|
auto packed = t.serialize_value(to_bytes_vec({"el1", "el2", "el3"}));
|
|
auto i = t.begin(packed);
|
|
auto end = t.end(packed);
|
|
BOOST_REQUIRE_EQUAL(to_bytes("el1"), *i++);
|
|
BOOST_REQUIRE_EQUAL(to_bytes("el2"), *i++);
|
|
BOOST_REQUIRE_EQUAL(to_bytes("el3"), *i++);
|
|
BOOST_REQUIRE(i == end);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_conversion_to_legacy_form) {
|
|
compound_type<allow_prefixes::no> singular({bytes_type});
|
|
|
|
BOOST_REQUIRE_EQUAL(to_legacy(singular, singular.serialize_single(to_bytes("asd"))), bytes("asd"));
|
|
BOOST_REQUIRE_EQUAL(to_legacy(singular, singular.serialize_single(to_bytes(""))), bytes(""));
|
|
|
|
compound_type<allow_prefixes::no> two_components({bytes_type, bytes_type});
|
|
|
|
BOOST_REQUIRE_EQUAL(to_legacy(two_components, two_components.serialize_value(to_bytes_vec({"el1", "elem2"}))),
|
|
bytes({'\x00', '\x03', 'e', 'l', '1', '\x00', '\x00', '\x05', 'e', 'l', 'e', 'm', '2', '\x00'}));
|
|
|
|
BOOST_REQUIRE_EQUAL(to_legacy(two_components, two_components.serialize_value(to_bytes_vec({"el1", ""}))),
|
|
bytes({'\x00', '\x03', 'e', 'l', '1', '\x00', '\x00', '\x00', '\x00'}));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_legacy_ordering_of_singular) {
|
|
compound_type<allow_prefixes::no> t({bytes_type});
|
|
|
|
auto make = [&t] (sstring value) -> bytes {
|
|
return t.serialize_single(to_bytes(value));
|
|
};
|
|
|
|
legacy_compound_view<decltype(t)>::tri_comparator cmp(t);
|
|
|
|
BOOST_REQUIRE(cmp(make("A"), make("B")) < 0);
|
|
BOOST_REQUIRE(cmp(make("AA"), make("B")) < 0);
|
|
BOOST_REQUIRE(cmp(make("B"), make("AB")) > 0);
|
|
BOOST_REQUIRE(cmp(make("B"), make("A")) > 0);
|
|
BOOST_REQUIRE(cmp(make("A"), make("A")) == 0);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_legacy_ordering_of_composites) {
|
|
compound_type<allow_prefixes::no> t({bytes_type, bytes_type});
|
|
|
|
auto make = [&t] (sstring v1, sstring v2) -> bytes {
|
|
return t.serialize_value(std::vector<bytes>{to_bytes(v1), to_bytes(v2)});
|
|
};
|
|
|
|
legacy_compound_view<decltype(t)>::tri_comparator cmp(t);
|
|
|
|
BOOST_REQUIRE(cmp(make("A", "B"), make("A", "B")) == 0);
|
|
BOOST_REQUIRE(cmp(make("A", "B"), make("A", "C")) < 0);
|
|
BOOST_REQUIRE(cmp(make("A", "B"), make("B", "B")) < 0);
|
|
BOOST_REQUIRE(cmp(make("A", "C"), make("B", "B")) < 0);
|
|
BOOST_REQUIRE(cmp(make("B", "A"), make("A", "A")) > 0);
|
|
|
|
BOOST_REQUIRE(cmp(make("AA", "B"), make("B", "B")) > 0);
|
|
BOOST_REQUIRE(cmp(make("A", "AA"), make("A", "A")) > 0);
|
|
|
|
BOOST_REQUIRE(cmp(make("", "A"), make("A", "A")) < 0);
|
|
BOOST_REQUIRE(cmp(make("A", ""), make("A", "A")) < 0);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_enconding_of_legacy_composites) {
|
|
using components = std::vector<composite::component>;
|
|
|
|
BOOST_REQUIRE_EQUAL(composite(bytes({'\x00', '\x03', 'e', 'l', '1', '\x00'})).components(),
|
|
components({std::make_pair(bytes("el1"), composite::eoc::none)}));
|
|
BOOST_REQUIRE_EQUAL(composite(bytes({'\x00', '\x00', '\x01'})).components(),
|
|
components({std::make_pair(bytes(""), composite::eoc::end)}));
|
|
BOOST_REQUIRE_EQUAL(composite(bytes({'\x00', '\x05', 'e', 'l', 'e', 'm', '1', '\xff'})).components(),
|
|
components({std::make_pair(bytes("elem1"), composite::eoc::start)}));
|
|
BOOST_REQUIRE_EQUAL(composite(bytes({'\x00', '\x03', 'e', 'l', '1', '\x05'})).components(),
|
|
components({std::make_pair(bytes("el1"), composite::eoc::end)}));
|
|
|
|
|
|
BOOST_REQUIRE_EQUAL(composite(bytes({'\x00', '\x03', 'e', 'l', '1', '\x00', '\x00', '\x05', 'e', 'l', 'e', 'm', '2', '\x01'})).components(),
|
|
components({std::make_pair(bytes("el1"), composite::eoc::none),
|
|
std::make_pair(bytes("elem2"), composite::eoc::end)}));
|
|
|
|
BOOST_REQUIRE_EQUAL(composite(bytes({'\x00', '\x03', 'e', 'l', '1', '\xff', '\x00', '\x00', '\x01'})).components(),
|
|
components({std::make_pair(bytes("el1"), composite::eoc::start),
|
|
std::make_pair(bytes(""), composite::eoc::end)}));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_enconding_of_singular_composite) {
|
|
using components = std::vector<composite::component>;
|
|
|
|
BOOST_REQUIRE_EQUAL(composite(bytes({'e', 'l', '1'}), false).components(),
|
|
components({std::make_pair(bytes("el1"), composite::eoc::none)}));
|
|
|
|
BOOST_REQUIRE_EQUAL(composite::serialize_value(std::vector<bytes>({bytes({'e', 'l', '1'})}), false).components(),
|
|
components({std::make_pair(bytes("el1"), composite::eoc::none)}));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_enconding_of_static_composite) {
|
|
using components = std::vector<composite::component>;
|
|
|
|
auto s = schema_builder("ks", "cf")
|
|
.with_column("pk", bytes_type, column_kind::partition_key)
|
|
.with_column("ck", bytes_type, column_kind::clustering_key)
|
|
.with_column("v", bytes_type, column_kind::regular_column)
|
|
.build();
|
|
auto c = composite::static_prefix(*s);
|
|
BOOST_REQUIRE(c.is_static());
|
|
components cs;
|
|
for (auto&& p : c.components()) {
|
|
cs.push_back(std::make_pair(to_bytes(p.first), p.second));
|
|
}
|
|
BOOST_REQUIRE_EQUAL(cs, components({std::make_pair(bytes(""), composite::eoc::none)}));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_composite_serialize_value) {
|
|
BOOST_REQUIRE_EQUAL(composite::serialize_value(std::vector<bytes>({bytes({'e', 'l', '1'})})).release_bytes(),
|
|
bytes({'\x00', '\x03', 'e', 'l', '1', '\x00'}));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_composite_from_exploded) {
|
|
using components = std::vector<composite::component>;
|
|
BOOST_REQUIRE_EQUAL(composite::from_exploded({bytes_view(bytes({'e', 'l', '1'}))}, true, composite::eoc::start).components(),
|
|
components({std::make_pair(bytes("el1"), composite::eoc::start)}));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_composite_view_explode) {
|
|
auto to_owning_vector = [] (std::vector<bytes_view> bvs) {
|
|
return boost::copy_range<std::vector<bytes>>(bvs | boost::adaptors::transformed([] (auto bv) {
|
|
return bytes(bv.begin(), bv.end());
|
|
}));
|
|
};
|
|
{
|
|
BOOST_REQUIRE_EQUAL(to_owning_vector(composite_view(composite(bytes({'\x00', '\x03', 'e', 'l', '1', '\x00'}))).explode()),
|
|
std::vector<bytes>({bytes({'e', 'l', '1'})}));
|
|
}
|
|
|
|
{
|
|
BOOST_REQUIRE_EQUAL(to_owning_vector(composite_view(composite(bytes({'e', 'l', '1'}), false)).explode()),
|
|
std::vector<bytes>({bytes({'e', 'l', '1'})}));
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_composite_validity) {
|
|
auto is_valid = [] (bytes b) {
|
|
auto v = composite_view(b);
|
|
try {
|
|
size_t s = 0;
|
|
for (auto& c : v.components()) { s += c.first.size() + sizeof(composite::size_type) + sizeof(composite::eoc_type); }
|
|
return s == b.size();
|
|
} catch (marshal_exception&) {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
BOOST_REQUIRE_EQUAL(is_valid({'\x00', '\x01', 'a', '\x00'}), true);
|
|
BOOST_REQUIRE_EQUAL(is_valid({'\x00', '\x02', 'a', 'a', '\x00'}), true);
|
|
BOOST_REQUIRE_EQUAL(is_valid({'\x00', '\x02', 'a', 'a', '\x00', '\x00', '\x01', 'a', '\x00'}), true);
|
|
|
|
BOOST_REQUIRE_EQUAL(is_valid({'\x00', '\x02', 'a', '\x00'}), false);
|
|
BOOST_REQUIRE_EQUAL(is_valid({'\x01', 'a', '\x00'}), false);
|
|
BOOST_REQUIRE_EQUAL(is_valid({'\x00', '\x01', 'a', '\x00', '\x00'}), false);
|
|
BOOST_REQUIRE_EQUAL(is_valid({'\x00', '\x01', 'a', '\x00', '\x00', '\x00'}), false);
|
|
BOOST_REQUIRE_EQUAL(is_valid({'\x00', '\x01', 'a', '\x00', '\x00', '\x01'}), false);
|
|
BOOST_REQUIRE_EQUAL(is_valid({'\x00', '\x01', 'a'}), false);
|
|
BOOST_REQUIRE_EQUAL(is_valid({'\x00', '\x02', 'a'}), false);
|
|
}
|