Skip to content

Commit a16c652

Browse files
transport: implement SCYLLA_USE_METADATA_ID support
Metadata id was introduced in CQLv5 to make metadata of prepared statement consistent between driver and database. This commit introduces a protocol extension that allows to use the same mechanism in CQLv4. This change: - Introduce SCYLLA_USE_METADATA_ID protocol extension for CQLv4 - Introduce METADATA_CHANGED flag in RESULT. The flag cames directly from CQLv5 binary protocol. In CQLv4, the bit was never used, so we assume it is safe to reuse it. - Implement handling of metadata_id and METADATA_CHANGED in RESULT rows - Implement returning metadata_id in RESULT prepared - Implement reading metadata_id from EXECUTE - Added description of SCYLLA_USE_METADATA_ID in documentation Metadata_id is wrapped in cql_metadata_id_wrapper because we need to distinguish the following situations: - Metadata_id is not supported by the protocol (e.g. CQLv4 without the extension is used) - Metadata_id is supported by the protocol but not set - e.g. PREPARE query is being handled: it doesn't contain metadata_id in the request but the reply (RESULT prepared) must contain metadata_id - Metadata_id is supported by the protocol and set, any number of bytes >= 0 is allowed, according to the CQLv5 protocol specification Fixes scylladb#20860
1 parent 2e3b5d4 commit a16c652

File tree

7 files changed

+118
-19
lines changed

7 files changed

+118
-19
lines changed

cql3/result_set.hh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@ public:
2727
GLOBAL_TABLES_SPEC = 0,
2828
HAS_MORE_PAGES = 1,
2929
NO_METADATA = 2,
30+
METADATA_CHANGED = 3,
3031
};
3132

3233
using flag_enum = super_enum<flag,
3334
flag::GLOBAL_TABLES_SPEC,
3435
flag::HAS_MORE_PAGES,
35-
flag::NO_METADATA>;
36+
flag::NO_METADATA,
37+
flag::METADATA_CHANGED>;
3638

3739
using flag_enum_set = enum_set<flag_enum>;
3840

docs/dev/protocol-extensions.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,15 @@ Having a designated flag gives the ability to skip tablet metadata generation
224224

225225
The feature is identified by the `TABLETS_ROUTING_V1` key, which is meant to be sent
226226
in the SUPPORTED message.
227+
228+
## Negotiate sending metadata id
229+
230+
This extension allows the driver to inform the database that it is aware of
231+
`metadata id` and is able to interpret the metadata id information.
232+
233+
Metadata id was originally introduced in CQLv5 to make metadata of prepared statement
234+
consistent between driver and database. This extension allows to use
235+
the same mechanism for other protocol versions, such as CQLv4.
236+
237+
The feature is identified by the `SCYLLA_USE_METADATA_ID` key, which is meant to be sent
238+
in the SUPPORTED message.

transport/cql_protocol_extension.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ namespace cql_transport {
1818
static const std::map<cql_protocol_extension, seastar::sstring> EXTENSION_NAMES = {
1919
{cql_protocol_extension::LWT_ADD_METADATA_MARK, "SCYLLA_LWT_ADD_METADATA_MARK"},
2020
{cql_protocol_extension::RATE_LIMIT_ERROR, "SCYLLA_RATE_LIMIT_ERROR"},
21-
{cql_protocol_extension::TABLETS_ROUTING_V1, "TABLETS_ROUTING_V1"}
21+
{cql_protocol_extension::TABLETS_ROUTING_V1, "TABLETS_ROUTING_V1"},
22+
{cql_protocol_extension::USE_METADATA_ID, "SCYLLA_USE_METADATA_ID"}
2223
};
2324

2425
cql_protocol_extension_enum_set supported_cql_protocol_extensions() {

transport/cql_protocol_extension.hh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ namespace cql_transport {
2828
enum class cql_protocol_extension {
2929
LWT_ADD_METADATA_MARK,
3030
RATE_LIMIT_ERROR,
31-
TABLETS_ROUTING_V1
31+
TABLETS_ROUTING_V1,
32+
USE_METADATA_ID
3233
};
3334

3435
using cql_protocol_extension_enum = super_enum<cql_protocol_extension,
3536
cql_protocol_extension::LWT_ADD_METADATA_MARK,
3637
cql_protocol_extension::RATE_LIMIT_ERROR,
37-
cql_protocol_extension::TABLETS_ROUTING_V1>;
38+
cql_protocol_extension::TABLETS_ROUTING_V1,
39+
cql_protocol_extension::USE_METADATA_ID>;
3840

3941
using cql_protocol_extension_enum_set = enum_set<cql_protocol_extension_enum>;
4042

transport/response.hh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#pragma once
1010

11+
#include "cql3/prepared_statements_cache.hh"
1112
#include "server.hh"
1213

1314
namespace cql_transport {
@@ -76,7 +77,7 @@ public:
7677
void write_string_bytes_map(const std::unordered_map<sstring, bytes>& map);
7778
void write_value(bytes_opt value);
7879
void write_value(std::optional<managed_bytes_view> value);
79-
void write(const cql3::metadata& m, bool skip = false);
80+
void write(const cql3::metadata& m, const cql_metadata_id_wrapper& metadata_id, bool no_metadata = false);
8081
void write(const cql3::prepared_metadata& m, uint8_t version);
8182

8283
// Make a non-owning scattered_message of the response. Remains valid as long

transport/server.cc

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "server.hh"
1010

11+
#include "cql3/prepared_statements_cache.hh"
1112
#include "cql3/statements/batch_statement.hh"
1213
#include "cql3/statements/modification_statement.hh"
1314
#include <seastar/core/scheduling.hh>
@@ -27,6 +28,7 @@
2728
#include <seastar/core/coroutine.hh>
2829
#include <seastar/core/future-util.hh>
2930
#include <seastar/core/seastar.hh>
31+
#include <seastar/core/shared_ptr.hh>
3032
#include <seastar/net/byteorder.hh>
3133
#include <seastar/core/metrics.hh>
3234
#include <seastar/net/byteorder.hh>
@@ -174,6 +176,12 @@ sstring to_string(const event::schema_change::target_type t) {
174176
SCYLLA_ASSERT(false && "unreachable");
175177
}
176178

179+
bool is_metadata_id_supported(const service::client_state& client_state) {
180+
// TODO: metadata_id is mandatory in CQLv5, so extend the check below
181+
// when CQLv5 support is implemented
182+
return client_state.is_protocol_extension_set(cql_transport::cql_protocol_extension::USE_METADATA_ID);
183+
}
184+
177185
event::event_type parse_event_type(const sstring& value)
178186
{
179187
if (value == "TOPOLOGY_CHANGE") {
@@ -982,7 +990,7 @@ future<std::unique_ptr<cql_server::response>> cql_server::connection::process_op
982990

983991
std::unique_ptr<cql_server::response>
984992
make_result(int16_t stream, messages::result_message& msg, const tracing::trace_state_ptr& tr_state,
985-
cql_protocol_version_type version, bool skip_metadata = false);
993+
cql_protocol_version_type version, cql_metadata_id_wrapper&& metadata_id, bool skip_metadata = false);
986994

987995
template <typename Process>
988996
requires std::is_invocable_r_v<future<cql_server::process_fn_return_type>,
@@ -1078,7 +1086,9 @@ process_query_internal(service::client_state& client_state, distributed<cql3::qu
10781086
return cql_server::process_fn_return_type(convert_error_message_to_coordinator_result(msg.get()));
10791087
} else {
10801088
tracing::trace(q_state->query_state.get_trace_state(), "Done processing - preparing a result");
1081-
return cql_server::process_fn_return_type(make_foreign(make_result(stream, *msg, q_state->query_state.get_trace_state(), version, skip_metadata)));
1089+
cql_metadata_id_wrapper metadata_id{is_metadata_id_supported(q_state->query_state.get_client_state())};
1090+
1091+
return cql_server::process_fn_return_type(make_foreign(make_result(stream, *msg, q_state->query_state.get_trace_state(), version, std::move(metadata_id), skip_metadata)));
10821092
}
10831093
});
10841094
}
@@ -1101,11 +1111,13 @@ future<std::unique_ptr<cql_server::response>> cql_server::connection::process_pr
11011111
return qp.prepare(std::move(query), client_state, dialect).discard_result();
11021112
}).then([this, query, stream, &client_state, trace_state, dialect] () mutable {
11031113
tracing::trace(trace_state, "Done preparing on remote shards");
1104-
return _server._query_processor.local().prepare(std::move(query), client_state, dialect).then([this, stream, trace_state] (auto msg) {
1114+
cql_metadata_id_wrapper metadata_id{is_metadata_id_supported(client_state)};
1115+
1116+
return _server._query_processor.local().prepare(std::move(query), client_state, dialect).then([this, stream, trace_state, metadata_id = std::move(metadata_id)] (auto msg) mutable {
11051117
tracing::trace(trace_state, "Done preparing on a local shard - preparing a result. ID is [{}]", seastar::value_of([&msg] {
11061118
return messages::result_message::prepared::cql::get_id(msg);
11071119
}));
1108-
return make_result(stream, *msg, trace_state, _version);
1120+
return make_result(stream, *msg, trace_state, _version, std::move(metadata_id));
11091121
});
11101122
});
11111123
}
@@ -1116,6 +1128,11 @@ process_execute_internal(service::client_state& client_state, distributed<cql3::
11161128
service_permit permit, tracing::trace_state_ptr trace_state, bool init_trace, cql3::computed_function_values cached_pk_fn_calls,
11171129
cql3::dialect dialect) {
11181130
cql3::prepared_cache_key_type cache_key(in.read_short_bytes(), dialect);
1131+
1132+
cql_metadata_id_wrapper metadata_id = is_metadata_id_supported(client_state)
1133+
? cql_metadata_id_wrapper(cql3::cql_metadata_id_type(in.read_short_bytes()))
1134+
: cql_metadata_id_wrapper(false);
1135+
11191136
auto& id = cql3::prepared_cache_key_type::cql_id(cache_key);
11201137
bool needs_authorization = false;
11211138

@@ -1169,14 +1186,14 @@ process_execute_internal(service::client_state& client_state, distributed<cql3::
11691186

11701187
tracing::trace(trace_state, "Processing a statement");
11711188
return qp.local().execute_prepared_without_checking_exception_message(query_state, std::move(stmt), options, std::move(prepared), std::move(cache_key), needs_authorization)
1172-
.then([trace_state = query_state.get_trace_state(), skip_metadata, q_state = std::move(q_state), stream, version] (auto msg) {
1189+
.then([trace_state = query_state.get_trace_state(), skip_metadata, q_state = std::move(q_state), stream, version, metadata_id = std::move(metadata_id)] (auto msg) mutable {
11731190
if (msg->move_to_shard()) {
11741191
return cql_server::process_fn_return_type(make_foreign(dynamic_pointer_cast<messages::result_message::bounce_to_shard>(msg)));
11751192
} else if (msg->is_exception()) {
11761193
return cql_server::process_fn_return_type(convert_error_message_to_coordinator_result(msg.get()));
11771194
} else {
11781195
tracing::trace(q_state->query_state.get_trace_state(), "Done processing - preparing a result");
1179-
return cql_server::process_fn_return_type(make_foreign(make_result(stream, *msg, q_state->query_state.get_trace_state(), version, skip_metadata)));
1196+
return cql_server::process_fn_return_type(make_foreign(make_result(stream, *msg, q_state->query_state.get_trace_state(), version, std::move(metadata_id), skip_metadata)));
11801197
}
11811198
});
11821199
}
@@ -1296,7 +1313,9 @@ process_batch_internal(service::client_state& client_state, distributed<cql3::qu
12961313
return cql_server::process_fn_return_type(convert_error_message_to_coordinator_result(msg.get()));
12971314
} else {
12981315
tracing::trace(q_state->query_state.get_trace_state(), "Done processing - preparing a result");
1299-
return cql_server::process_fn_return_type(make_foreign(make_result(stream, *msg, trace_state, version)));
1316+
cql_metadata_id_wrapper metadata_id{is_metadata_id_supported(q_state->query_state.get_client_state())};
1317+
1318+
return cql_server::process_fn_return_type(make_foreign(make_result(stream, *msg, trace_state, version, std::move(metadata_id))));
13001319
}
13011320
});
13021321
}
@@ -1510,11 +1529,13 @@ class cql_server::fmt_visitor : public messages::result_message::visitor_base {
15101529
uint8_t _version;
15111530
cql_server::response& _response;
15121531
bool _skip_metadata;
1532+
cql_metadata_id_wrapper _metadata_id;
15131533
public:
1514-
fmt_visitor(uint8_t version, cql_server::response& response, bool skip_metadata)
1534+
fmt_visitor(uint8_t version, cql_server::response& response, bool skip_metadata, cql_metadata_id_wrapper&& metadata_id)
15151535
: _version{version}
15161536
, _response{response}
15171537
, _skip_metadata{skip_metadata}
1538+
, _metadata_id(std::move(metadata_id))
15181539
{ }
15191540

15201541
virtual void visit(const messages::result_message::void_message&) override {
@@ -1529,8 +1550,11 @@ class cql_server::fmt_visitor : public messages::result_message::visitor_base {
15291550
virtual void visit(const messages::result_message::prepared::cql& m) override {
15301551
_response.write_int(0x0004);
15311552
_response.write_short_bytes(m.get_id());
1553+
if (_metadata_id.is_supported_by_protocol()) {
1554+
_response.write_short_bytes(m.result_metadata()->calculate_metadata_id()._metadata_id);
1555+
}
15321556
_response.write(m.metadata(), _version);
1533-
_response.write(*m.result_metadata());
1557+
_response.write(*m.result_metadata(), _metadata_id);
15341558
}
15351559

15361560
virtual void visit(const messages::result_message::schema_change& m) override {
@@ -1550,7 +1574,7 @@ class cql_server::fmt_visitor : public messages::result_message::visitor_base {
15501574
virtual void visit(const messages::result_message::rows& m) override {
15511575
_response.write_int(0x0002);
15521576
auto& rs = m.rs();
1553-
_response.write(rs.get_metadata(), _skip_metadata);
1577+
_response.write(rs.get_metadata(), _metadata_id, _skip_metadata);
15541578
auto row_count_plhldr = _response.write_int_placeholder();
15551579

15561580
class visitor {
@@ -1578,7 +1602,7 @@ class cql_server::fmt_visitor : public messages::result_message::visitor_base {
15781602

15791603
std::unique_ptr<cql_server::response>
15801604
make_result(int16_t stream, messages::result_message& msg, const tracing::trace_state_ptr& tr_state,
1581-
cql_protocol_version_type version, bool skip_metadata) {
1605+
cql_protocol_version_type version, cql_metadata_id_wrapper&& metadata_id, bool skip_metadata) {
15821606
auto response = std::make_unique<cql_server::response>(stream, cql_binary_opcode::RESULT, tr_state);
15831607
if (__builtin_expect(!msg.warnings().empty() && version > 3, false)) {
15841608
response->set_frame_flag(cql_frame_flags::warning);
@@ -1588,7 +1612,7 @@ make_result(int16_t stream, messages::result_message& msg, const tracing::trace_
15881612
response->set_frame_flag(cql_frame_flags::custom_payload);
15891613
response->write_string_bytes_map(msg.custom_payload().value());
15901614
}
1591-
cql_server::fmt_visitor fmt{version, *response, skip_metadata};
1615+
cql_server::fmt_visitor fmt{version, *response, skip_metadata, std::move(metadata_id)};
15921616
msg.accept(fmt);
15931617
return response;
15941618
}
@@ -1996,7 +2020,7 @@ thread_local const type_codec::type_id_to_type_type type_codec::type_id_to_type
19962020
{ inet_addr_type, type_id::INET },
19972021
};
19982022

1999-
void cql_server::response::write(const cql3::metadata& m, bool no_metadata) {
2023+
void cql_server::response::write(const cql3::metadata& m, const cql_metadata_id_wrapper& metadata_id, bool no_metadata) {
20002024
auto flags = m.flags();
20012025
bool global_tables_spec = m.flags().contains<cql3::metadata::flag::GLOBAL_TABLES_SPEC>();
20022026
bool has_more_pages = m.flags().contains<cql3::metadata::flag::HAS_MORE_PAGES>();
@@ -2005,6 +2029,18 @@ void cql_server::response::write(const cql3::metadata& m, bool no_metadata) {
20052029
flags.set<cql3::metadata::flag::NO_METADATA>();
20062030
}
20072031

2032+
cql3::cql_metadata_id_type calculated_metadata_id{bytes{}};
2033+
if (metadata_id.is_non_empty())
2034+
{
2035+
calculated_metadata_id = m.calculate_metadata_id();
2036+
if (calculated_metadata_id != metadata_id.get_metadata_id())
2037+
{
2038+
flags.remove<cql3::metadata::flag::NO_METADATA>();
2039+
flags.set<cql3::metadata::flag::METADATA_CHANGED>();
2040+
no_metadata = false;
2041+
}
2042+
}
2043+
20082044
write_int(flags.mask());
20092045
write_int(m.column_count());
20102046

@@ -2016,6 +2052,10 @@ void cql_server::response::write(const cql3::metadata& m, bool no_metadata) {
20162052
return;
20172053
}
20182054

2055+
if (flags.contains<cql3::metadata::flag::METADATA_CHANGED>()) {
2056+
write_bytes_as_string(calculated_metadata_id._metadata_id);
2057+
}
2058+
20192059
auto names_i = m.get_names().begin();
20202060

20212061
if (global_tables_spec) {
@@ -2068,6 +2108,13 @@ void cql_server::response::write(const cql3::prepared_metadata& m, uint8_t versi
20682108
}
20692109
}
20702110

2111+
const cql3::cql_metadata_id_type& cql_metadata_id_wrapper::get_metadata_id() const {
2112+
if (_state != state::SUPPORTED_BY_PROTOCOL_NON_EMPTY) {
2113+
on_internal_error(clogger, "Metadata id is unsupported or empty");
2114+
}
2115+
return _metadata_id;
2116+
}
2117+
20712118
future<utils::chunked_vector<client_data>> cql_server::get_client_data() {
20722119
utils::chunked_vector<client_data> ret;
20732120
co_await for_each_gently([&ret] (const generic_server::connection& c) {

transport/server.hh

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "auth/service.hh"
1212
#include <seastar/core/seastar.hh>
1313
#include <seastar/core/scheduling.hh>
14+
#include "cql3/prepared_statements_cache.hh"
1415
#include "service/endpoint_lifecycle_subscriber.hh"
1516
#include "service/migration_listener.hh"
1617
#include "auth/authenticator.hh"
@@ -148,6 +149,39 @@ struct connection_service_level_params {
148149
sstring scheduling_group_name;
149150
};
150151

152+
class cql_metadata_id_wrapper {
153+
public:
154+
enum class state {
155+
UNSUPPORTED_BY_PROTOCOL,
156+
SUPPORTED_BY_PROTOCOL_EMPTY, // metadata_id supported by protocol but no value provided (e.g. handling QUERY or BATCH)
157+
SUPPORTED_BY_PROTOCOL_NON_EMPTY // metadata_id supported by protocol and provided (i.e. handling EXECUTE)
158+
};
159+
private:
160+
cql3::cql_metadata_id_type _metadata_id;
161+
state _state;
162+
163+
public:
164+
explicit cql_metadata_id_wrapper(bool supported)
165+
: _metadata_id{bytes{}}
166+
, _state(supported ? state::SUPPORTED_BY_PROTOCOL_EMPTY : state::UNSUPPORTED_BY_PROTOCOL)
167+
{ }
168+
169+
explicit cql_metadata_id_wrapper(cql3::cql_metadata_id_type&& metadata_id)
170+
: _metadata_id(std::move(metadata_id))
171+
, _state(state::SUPPORTED_BY_PROTOCOL_NON_EMPTY)
172+
{ }
173+
174+
bool is_supported_by_protocol() const {
175+
return _state != state::UNSUPPORTED_BY_PROTOCOL;
176+
}
177+
178+
bool is_non_empty() const {
179+
return _state == state::SUPPORTED_BY_PROTOCOL_NON_EMPTY;
180+
}
181+
182+
const cql3::cql_metadata_id_type& get_metadata_id() const;
183+
};
184+
151185
class cql_server : public seastar::peering_sharded_service<cql_server>, public generic_server::server {
152186
private:
153187
struct transport_stats {
@@ -212,7 +246,7 @@ private:
212246
class fmt_visitor;
213247
friend class connection;
214248
friend std::unique_ptr<cql_server::response> make_result(int16_t stream, messages::result_message& msg,
215-
const tracing::trace_state_ptr& tr_state, cql_protocol_version_type version, bool skip_metadata);
249+
const tracing::trace_state_ptr& tr_state, cql_protocol_version_type version, cql_metadata_id_wrapper&& metadata_id, bool skip_metadata);
216250

217251
class connection : public generic_server::connection {
218252
cql_server& _server;

0 commit comments

Comments
 (0)