mirror of
https://github.com/scylladb/scylladb.git
synced 2026-05-23 08:12:08 +00:00
Prepared LIST statements were not calculating metadata in PREPARE path, and sent empty string hash to client causing problematic behaviour where metadat_id was not recalculated correctly. This patch moves metadata construction into get_result_metadata() for the affected LIST statements and reuse that metadata when building the result set. This gives PREPARE a stable metadata id for LIST ROLES, LIST USERS, LIST PERMISSIONS and the service-level variants. This patch also adds a new boost test that verifies that when an EXECUTE request carries an empty result metadata id while the server has a real metadata id for the result set, the response is marked METADATA_CHANGED and includes the full result metadata plus the server metadata id. This covers the recovery path for clients that send an empty or otherwise unusable metadata id instead of a matching cached one.
203 lines
8.6 KiB
C++
203 lines
8.6 KiB
C++
/*
|
|
* Copyright (C) 2018-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.1
|
|
*/
|
|
|
|
#include "test/lib/scylla_test_case.hh"
|
|
|
|
#include <fmt/ranges.h>
|
|
#include <fmt/std.h>
|
|
|
|
#include "transport/request.hh"
|
|
#include "transport/response.hh"
|
|
#include "cql3/column_identifier.hh"
|
|
#include "utils/memory_data_sink.hh"
|
|
#include "test/lib/random_utils.hh"
|
|
#include "test/lib/test_utils.hh"
|
|
|
|
namespace cql3 {
|
|
|
|
bool operator==(const cql3::raw_value_view& a, const cql3::raw_value_view& b) {
|
|
if (a.is_value()) {
|
|
return b.is_value() && b.with_value([&] (const FragmentedView auto& v2) {
|
|
return a.with_value([&] (const FragmentedView auto& v1) {
|
|
return equal_unsigned(v1, v2);
|
|
});
|
|
});
|
|
} else {
|
|
return a.is_null() == b.is_null();
|
|
}
|
|
}
|
|
|
|
} // namespace cql3
|
|
|
|
SEASTAR_THREAD_TEST_CASE(test_response_request_reader) {
|
|
auto stream_id = tests::random::get_int<int16_t>();
|
|
auto opcode = tests::random::get_int<uint8_t>(uint8_t(cql_transport::cql_binary_opcode::AUTH_SUCCESS));
|
|
auto res = cql_transport::response(stream_id, cql_transport::cql_binary_opcode(opcode), tracing::trace_state_ptr());
|
|
|
|
// Null value
|
|
res.write_value(bytes_opt());
|
|
|
|
// Unset value
|
|
res.write_int(-2);
|
|
|
|
// "Value" value
|
|
auto value = tests::random::get_bytes(tests::random::get_int<int16_t>(1024));
|
|
res.write_value(bytes_opt(value));
|
|
|
|
// Name and value list
|
|
auto names_and_values =
|
|
std::views::iota(0, tests::random::get_int<int>(16) + 16)
|
|
| std::views::transform([] (int) {
|
|
return std::pair(
|
|
tests::random::get_sstring(),
|
|
!tests::random::get_int(4) ? bytes_opt() : bytes_opt(tests::random::get_bytes(tests::random::get_int<int16_t>(1024)))
|
|
);
|
|
})
|
|
| std::ranges::to<std::vector<std::pair<sstring, bytes_opt>>>();
|
|
res.write_short(names_and_values.size());
|
|
for (auto& [ name, value ] : names_and_values) {
|
|
res.write_string(name);
|
|
res.write_value(value);
|
|
}
|
|
|
|
// String list
|
|
auto string_list =
|
|
std::views::iota(0, tests::random::get_int<int>(16) + 16)
|
|
| std::views::transform([] (int) {
|
|
return tests::random::get_sstring();
|
|
})
|
|
| std::ranges::to<std::vector<sstring>>();
|
|
res.write_string_list(string_list);
|
|
|
|
// String map
|
|
auto string_map =
|
|
std::views::iota(0, tests::random::get_int<int>(16) + 16)
|
|
| std::views::transform([] (int) {
|
|
return std::pair(tests::random::get_sstring(), tests::random::get_sstring());
|
|
})
|
|
| std::ranges::to<std::map>();
|
|
res.write_string_map(string_map);
|
|
auto string_unordered_map = std::unordered_map<sstring, sstring>(string_map.begin(), string_map.end());
|
|
|
|
static constexpr auto version = 4;
|
|
|
|
using sc = cql_transport::event::schema_change;
|
|
res.serialize({sc::change_type::CREATED, sc::target_type::KEYSPACE, "foo"}, version);
|
|
res.serialize({sc::change_type::CREATED, sc::target_type::TABLE, "foo", "bar"}, version);
|
|
res.serialize({sc::change_type::CREATED, sc::target_type::TYPE, "foo", "bar"}, version);
|
|
res.serialize({sc::change_type::CREATED, sc::target_type::FUNCTION, "foo", "bar", "zed"}, version);
|
|
res.serialize({sc::change_type::CREATED, sc::target_type::AGGREGATE, "foo", "bar", "zed"}, version);
|
|
|
|
memory_data_sink_buffers buffers;
|
|
{
|
|
output_stream<char> out(data_sink(std::make_unique<memory_data_sink>(buffers)));
|
|
res.write_message(out, version, cql_transport::cql_compression::none, deleter()).get();
|
|
}
|
|
auto total_length = buffers.size();
|
|
auto fbufs = fragmented_temporary_buffer(buffers.buffers() | std::views::as_rvalue | std::ranges::to<std::vector>(), total_length);
|
|
|
|
bytes_ostream linearization_buffer;
|
|
auto req = cql_transport::request_reader(fbufs.get_istream(), linearization_buffer);
|
|
BOOST_CHECK_EQUAL(unsigned(uint8_t(req.read_byte().value())), version | 0x80);
|
|
BOOST_CHECK_EQUAL(unsigned(req.read_byte().value()), 0); // flags
|
|
BOOST_CHECK_EQUAL(req.read_short().value(), stream_id);
|
|
BOOST_CHECK_EQUAL(unsigned(req.read_byte().value()), unsigned(opcode));
|
|
BOOST_CHECK_EQUAL(req.read_int().value() + 9, total_length);
|
|
|
|
auto v1 = req.read_value_view(version).value();
|
|
BOOST_CHECK(!v1.unset && v1.value.is_null());
|
|
auto v2 = req.read_value_view(version).value();
|
|
BOOST_CHECK(v2.unset);
|
|
BOOST_CHECK_EQUAL(to_bytes(req.read_value_view(version).value().value), value);
|
|
|
|
std::vector<std::string_view> names;
|
|
std::vector<cql3::raw_value_view> values;
|
|
cql3::unset_bind_variable_vector unset;
|
|
BOOST_CHECK(req.read_name_and_value_list(version, names, values, unset));
|
|
BOOST_CHECK(std::none_of(unset.begin(), unset.end(), std::identity()));
|
|
BOOST_CHECK(std::ranges::equal(names, names_and_values | std::views::transform([] (auto& name_and_value) {
|
|
return std::string_view(name_and_value.first);
|
|
})));
|
|
BOOST_CHECK(std::ranges::equal(values, names_and_values | std::views::transform([] (auto& name_and_value) {
|
|
if (!name_and_value.second) {
|
|
return cql3::raw_value_view::make_null();
|
|
}
|
|
return cql3::raw_value_view::make_value(fragmented_temporary_buffer::view(*name_and_value.second));
|
|
})));
|
|
|
|
auto received_string_list = std::vector<sstring>();
|
|
BOOST_CHECK(req.read_string_list(received_string_list));
|
|
BOOST_CHECK_EQUAL(received_string_list, string_list);
|
|
|
|
auto received_string_map = req.read_string_map().value();
|
|
BOOST_CHECK_EQUAL(received_string_map, string_unordered_map);
|
|
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "CREATED");
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "KEYSPACE");
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "foo");
|
|
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "CREATED");
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "TABLE");
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "foo");
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "bar");
|
|
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "CREATED");
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "TYPE");
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "foo");
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "bar");
|
|
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "CREATED");
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "FUNCTION");
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "foo");
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "bar");
|
|
BOOST_CHECK_EQUAL(req.read_short().value(), 1);
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "zed");
|
|
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "CREATED");
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "AGGREGATE");
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "foo");
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "bar");
|
|
BOOST_CHECK_EQUAL(req.read_short().value(), 1);
|
|
BOOST_CHECK_EQUAL(req.read_string().value(), "zed");
|
|
}
|
|
|
|
SEASTAR_THREAD_TEST_CASE(test_response_metadata_changed_for_empty_request_metadata_id) {
|
|
auto col = make_lw_shared<cql3::column_specification>(
|
|
"ks", "cf", ::make_shared<cql3::column_identifier>("v", true), utf8_type);
|
|
cql3::metadata m({col});
|
|
auto calculated_metadata_id = m.calculate_metadata_id();
|
|
auto expected_metadata_id = bytes(calculated_metadata_id._metadata_id);
|
|
|
|
auto res = cql_transport::response(0, cql_transport::cql_binary_opcode::RESULT, tracing::trace_state_ptr());
|
|
res.write(m, cql_transport::cql_metadata_id_wrapper(
|
|
cql3::cql_metadata_id_type(bytes{}),
|
|
cql3::cql_metadata_id_type(bytes(expected_metadata_id))), true);
|
|
|
|
memory_data_sink_buffers buffers;
|
|
{
|
|
output_stream<char> out(data_sink(std::make_unique<memory_data_sink>(buffers)));
|
|
res.write_message(out, 4, cql_transport::cql_compression::none, deleter()).get();
|
|
}
|
|
auto total_length = buffers.size();
|
|
auto fbufs = fragmented_temporary_buffer(buffers.buffers() | std::views::as_rvalue | std::ranges::to<std::vector>(), total_length);
|
|
|
|
bytes_ostream linearization_buffer;
|
|
auto req = cql_transport::request_reader(fbufs.get_istream(), linearization_buffer);
|
|
BOOST_REQUIRE(req.read_byte());
|
|
BOOST_REQUIRE(req.read_byte());
|
|
BOOST_REQUIRE(req.read_short());
|
|
BOOST_REQUIRE(req.read_byte());
|
|
BOOST_REQUIRE(req.read_int());
|
|
|
|
auto flags = req.read_int().value();
|
|
BOOST_CHECK(flags & cql3::metadata::flag_enum_set::mask_for<cql3::metadata::flag::METADATA_CHANGED>());
|
|
BOOST_CHECK(!(flags & cql3::metadata::flag_enum_set::mask_for<cql3::metadata::flag::NO_METADATA>()));
|
|
BOOST_CHECK_EQUAL(req.read_int().value(), 1);
|
|
BOOST_CHECK_EQUAL(req.read_short_bytes().value(), expected_metadata_id);
|
|
}
|