Files
scylladb/test/boost/keys_test.cc
Taras Veretilnyk 367eaf46c5 keys: from_nodetool_style_string don't split single partition keys
Users with single-column partition keys that contain colon characters
were unable to use certain REST APIs and 'nodetool' commands, because the
API split key by colon regardless of the partition key schema.

Affected commands:
- 'nodetool getendpoints'
- 'nodetool getsstables'
Affected endpoints:
- '/column_family/sstables/by_key'
- '/storage_service/natural_endpoints'

Refs: #16596 - This does not fully fix the issue, as users with compound
keys will face the issue if any column of the partition key contains
a colon character.

Closes scylladb/scylladb#24829
2025-08-14 19:52:04 +03:00

197 lines
7.3 KiB
C++

/*
* Copyright (C) 2015-present ScyllaDB
*/
/*
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
*/
#define BOOST_TEST_MODULE core
#include <boost/test/unit_test.hpp>
#include "keys/keys.hh"
#include "schema/schema.hh"
#include "schema/schema_builder.hh"
#include "types/types.hh"
#include "idl/keys.dist.hh"
#include "serializer_impl.hh"
#include "idl/keys.dist.impl.hh"
BOOST_AUTO_TEST_CASE(test_key_is_prefixed_by) {
auto s_ptr = schema_builder("", "")
.with_column("c1", bytes_type, column_kind::partition_key)
.with_column("c2", bytes_type, column_kind::clustering_key)
.with_column("c3", bytes_type, column_kind::clustering_key)
.with_column("c4", bytes_type, column_kind::clustering_key)
.build();
const schema& s = *s_ptr;
auto key = clustering_key::from_exploded(s, {bytes("a"), bytes("b"), bytes("c")});
BOOST_REQUIRE(key.is_prefixed_by(s, clustering_key_prefix::from_exploded(s, {bytes("a")})));
BOOST_REQUIRE(key.is_prefixed_by(s, clustering_key_prefix::from_exploded(s, {bytes("a"), bytes("b")})));
BOOST_REQUIRE(key.is_prefixed_by(s, clustering_key_prefix::from_exploded(s, {bytes("a"), bytes("b"), bytes("c")})));
BOOST_REQUIRE(!key.is_prefixed_by(s, clustering_key_prefix::from_exploded(s, {bytes()})));
BOOST_REQUIRE(!key.is_prefixed_by(s, clustering_key_prefix::from_exploded(s, {bytes("b"), bytes("c")})));
BOOST_REQUIRE(!key.is_prefixed_by(s, clustering_key_prefix::from_exploded(s, {bytes("a"), bytes("c"), bytes("b")})));
BOOST_REQUIRE(!key.is_prefixed_by(s, clustering_key_prefix::from_exploded(s, {bytes("abc")})));
BOOST_REQUIRE(!key.is_prefixed_by(s, clustering_key_prefix::from_exploded(s, {bytes("ab")})));
}
BOOST_AUTO_TEST_CASE(test_key_component_iterator) {
auto s_ptr = schema_builder("", "")
.with_column("c1", bytes_type, column_kind::partition_key)
.with_column("c2", bytes_type, column_kind::clustering_key)
.with_column("c3", bytes_type, column_kind::clustering_key)
.with_column("c4", bytes_type, column_kind::clustering_key)
.build();
const schema& s = *s_ptr;
auto key = clustering_key::from_exploded(s, {bytes("a"), bytes("b"), bytes("c")});
auto i = key.begin(s);
auto end = key.end(s);
BOOST_REQUIRE(i != end);
BOOST_REQUIRE(*i == bytes_view(bytes("a")));
++i;
BOOST_REQUIRE(i != end);
BOOST_REQUIRE(*i == bytes_view(bytes("b")));
++i;
BOOST_REQUIRE(i != end);
BOOST_REQUIRE(*i == bytes_view(bytes("c")));
++i;
BOOST_REQUIRE(i == end);
}
BOOST_AUTO_TEST_CASE(test_legacy_ordering_for_non_composite_key) {
auto s_ptr = schema_builder("", "")
.with_column("c1", bytes_type, column_kind::partition_key)
.build();
const schema& s = *s_ptr;
auto to_key = [&s] (sstring value) {
return partition_key::from_single_value(s, to_bytes(value));
};
auto cmp = [&s] (const partition_key& k1, const partition_key& k2) {
return k1.legacy_tri_compare(s, k2);
};
BOOST_REQUIRE(cmp(to_key("A"), to_key("B")) < 0);
BOOST_REQUIRE(cmp(to_key("AA"), to_key("B")) < 0);
BOOST_REQUIRE(cmp(to_key("B"), to_key("AB")) > 0);
BOOST_REQUIRE(cmp(to_key("B"), to_key("A")) > 0);
BOOST_REQUIRE(cmp(to_key("A"), to_key("A")) == 0);
}
BOOST_AUTO_TEST_CASE(test_legacy_ordering_for_composite_keys) {
auto s_ptr = schema_builder("", "")
.with_column("c1", bytes_type, column_kind::partition_key)
.with_column("c2", bytes_type, column_kind::partition_key)
.build();
const schema& s = *s_ptr;
auto to_key = [&s] (sstring v1, sstring v2) {
return partition_key::from_exploded(s, std::vector<bytes>{to_bytes(v1), to_bytes(v2)});
};
auto cmp = [&s] (const partition_key& k1, const partition_key& k2) {
return k1.legacy_tri_compare(s, k2);
};
BOOST_REQUIRE(cmp(to_key("A", "B"), to_key("A", "B")) == 0);
BOOST_REQUIRE(cmp(to_key("A", "B"), to_key("A", "C")) < 0);
BOOST_REQUIRE(cmp(to_key("A", "B"), to_key("B", "B")) < 0);
BOOST_REQUIRE(cmp(to_key("A", "C"), to_key("B", "B")) < 0);
BOOST_REQUIRE(cmp(to_key("B", "A"), to_key("A", "A")) > 0);
BOOST_REQUIRE(cmp(to_key("AA", "B"), to_key("B", "B")) > 0);
BOOST_REQUIRE(cmp(to_key("A", "AA"), to_key("A", "A")) > 0);
BOOST_REQUIRE(cmp(to_key("", "A"), to_key("A", "A")) < 0);
BOOST_REQUIRE(cmp(to_key("A", ""), to_key("A", "A")) < 0);
}
BOOST_AUTO_TEST_CASE(test_conversions_between_view_and_wrapper) {
auto s_ptr = schema_builder("", "")
.with_column("c1", bytes_type, column_kind::partition_key)
.build();
const schema& s = *s_ptr;
auto key = partition_key::from_deeply_exploded(s, {data_value(bytes("value"))});
partition_key_view key_view = key;
BOOST_REQUIRE(key_view.equal(s, key));
BOOST_REQUIRE(key.equal(s, key_view));
partition_key key2 = key_view;
BOOST_REQUIRE(key2.equal(s, key));
BOOST_REQUIRE(key.equal(s, key2));
BOOST_REQUIRE(*key.begin(s) == to_managed_bytes("value"));
}
template<typename T>
inline
T reserialize(const T& v) {
auto buf = ser::serialize_to_buffer<bytes>(v);
auto in = ser::as_input_stream(buf);
return ser::deserialize(in, std::type_identity<T>());
}
BOOST_AUTO_TEST_CASE(test_serialization) {
auto s = schema_builder("ks", "cf")
.with_column("pk", bytes_type, column_kind::partition_key)
.with_column("v", bytes_type)
.build();
auto pk_value = bytes("value");
partition_key key(std::vector<bytes>({pk_value}));
BOOST_REQUIRE(key.equal(*s, reserialize(key)));
}
BOOST_AUTO_TEST_CASE(test_from_nodetool_style_string_single_partition_key) {
auto s1 = schema_builder("", "")
.with_column("c1", utf8_type, column_kind::partition_key)
.with_column("c2", bytes_type, column_kind::clustering_key)
.with_column("c3", bytes_type, column_kind::clustering_key)
.with_column("c4", bytes_type, column_kind::clustering_key)
.build();
auto pk_value = bytes("value");
partition_key key1(std::vector<bytes>({pk_value}));
auto key2 = partition_key::from_nodetool_style_string(s1, "value");
BOOST_REQUIRE(key1.equal(*s1, key2));
auto pk_with_col_value = bytes("val:ue");
partition_key key_with_col(std::vector<bytes>({pk_with_col_value}));
BOOST_REQUIRE(key_with_col.equal(*s1, partition_key::from_nodetool_style_string(s1, "val:ue")));
}
BOOST_AUTO_TEST_CASE(test_from_nodetool_style_string_composite_partition_key) {
auto s2 = schema_builder("", "")
.with_column("c1", utf8_type, column_kind::partition_key)
.with_column("c2", utf8_type, column_kind::partition_key)
.with_column("c3", bytes_type, column_kind::clustering_key)
.with_column("c4", bytes_type, column_kind::clustering_key)
.build();
auto pk_value1 = bytes("value1");
auto pk_value2 = bytes("value2");
partition_key key3(std::vector<bytes>({pk_value1, pk_value2}));
auto key4 = partition_key::from_nodetool_style_string(s2, "value1:value2");
BOOST_REQUIRE(key3.equal(*s2, key4));
BOOST_REQUIRE_THROW(partition_key::from_nodetool_style_string(s2, "value1:value2:extra"), std::invalid_argument);
BOOST_REQUIRE_THROW(partition_key::from_nodetool_style_string(s2, "value1"), std::invalid_argument);
}