Skip to content

Commit 086df24

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 c32aba9 commit 086df24

File tree

11 files changed

+144
-19
lines changed

11 files changed

+144
-19
lines changed

cql3/query_processor.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ private:
544544
[this, &client_state, &id_getter, d](const prepared_cache_key_type& key, const sstring& query_string) {
545545
return _prepared_cache.get(key, [this, &query_string, &client_state, d] {
546546
auto prepared = get_statement(query_string, client_state, d);
547+
prepared->calculate_metadata_id();
547548
auto bound_terms = prepared->statement->get_bound_terms();
548549
if (bound_terms > std::numeric_limits<uint16_t>::max()) {
549550
throw exceptions::invalid_request_exception(

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

cql3/statements/prepared_statement.hh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ public:
5050
const std::vector<seastar::lw_shared_ptr<column_specification>> bound_names;
5151
const std::vector<uint16_t> partition_key_bind_indices;
5252
std::vector<sstring> warnings;
53+
private:
54+
cql_metadata_id_type _metadata_id;
5355

56+
public:
5457
prepared_statement(audit::audit_info_ptr&& audit_info, seastar::shared_ptr<cql_statement> statement_, std::vector<seastar::lw_shared_ptr<column_specification>> bound_names_,
5558
std::vector<uint16_t> partition_key_bind_indices, std::vector<sstring> warnings = {});
5659

@@ -64,6 +67,10 @@ public:
6467
checked_weak_ptr checked_weak_from_this() const {
6568
return checked_weak_ptr(this->weak_from_this());
6669
}
70+
71+
void calculate_metadata_id();
72+
73+
cql_metadata_id_type get_metadata_id() const;
6774
};
6875

6976
}

cql3/statements/raw/parsed_statement.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "cql3/column_specification.hh"
1515

1616
#include "cql3/cql_statement.hh"
17+
#include "cql3/result_set.hh"
1718

1819
namespace cql3 {
1920

@@ -47,6 +48,7 @@ prepared_statement::prepared_statement(
4748
, bound_names(std::move(bound_names_))
4849
, partition_key_bind_indices(std::move(partition_key_bind_indices))
4950
, warnings(std::move(warnings))
51+
, _metadata_id(bytes{})
5052
{
5153
statement->set_audit_info(std::move(audit_info));
5254
}
@@ -66,6 +68,14 @@ prepared_statement::prepared_statement(audit::audit_info_ptr&& audit_info, ::sha
6668
: prepared_statement(std::move(audit_info), statement_, std::vector<lw_shared_ptr<column_specification>>(), std::vector<uint16_t>(), std::move(warnings))
6769
{ }
6870

71+
void prepared_statement::calculate_metadata_id() {
72+
_metadata_id = statement->get_result_metadata()->calculate_metadata_id();
73+
}
74+
75+
cql_metadata_id_type prepared_statement::get_metadata_id() const {
76+
return _metadata_id;
77+
}
78+
6979
}
7080

7181
}

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/messages/result_message.hh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ public:
4848
return _result_metadata;
4949
}
5050

51+
cql3::cql_metadata_id_type get_metadata_id() const {
52+
return _prepared->get_metadata_id();
53+
}
54+
5155
class cql;
5256
private:
5357
static ::shared_ptr<const cql3::metadata> extract_result_metadata(::shared_ptr<cql3::cql_statement> statement);

transport/response.hh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public:
7676
void write_string_bytes_map(const std::unordered_map<sstring, bytes>& map);
7777
void write_value(bytes_opt value);
7878
void write_value(std::optional<managed_bytes_view> value);
79-
void write(const cql3::metadata& m, bool skip = false);
79+
void write(const cql3::metadata& m, const cql_metadata_id_wrapper& request_metadata_id, bool no_metadata = false);
8080
void write(const cql3::prepared_metadata& m, uint8_t version);
8181

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

transport/server.cc

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,12 @@ sstring to_string(const event::schema_change::target_type t) {
175175
SCYLLA_ASSERT(false && "unreachable");
176176
}
177177

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

10101016
std::unique_ptr<cql_server::response>
10111017
make_result(int16_t stream, messages::result_message& msg, const tracing::trace_state_ptr& tr_state,
1012-
cql_protocol_version_type version, bool skip_metadata = false);
1018+
cql_protocol_version_type version, cql_metadata_id_wrapper&& metadata_id, bool skip_metadata = false);
10131019

10141020
template <typename Process>
10151021
requires std::is_invocable_r_v<future<cql_server::process_fn_return_type>,
@@ -1104,7 +1110,8 @@ process_query_internal(service::client_state& client_state, distributed<cql3::qu
11041110
return cql_server::process_fn_return_type(convert_error_message_to_coordinator_result(msg.get()));
11051111
} else {
11061112
tracing::trace(q_state->query_state.get_trace_state(), "Done processing - preparing a result");
1107-
return cql_server::process_fn_return_type(make_foreign(make_result(stream, *msg, q_state->query_state.get_trace_state(), version, skip_metadata)));
1113+
1114+
return cql_server::process_fn_return_type(make_foreign(make_result(stream, *msg, q_state->query_state.get_trace_state(), version, cql_metadata_id_wrapper{}, skip_metadata)));
11081115
}
11091116
});
11101117
}
@@ -1127,11 +1134,14 @@ future<std::unique_ptr<cql_server::response>> cql_server::connection::process_pr
11271134
return qp.prepare(std::move(query), client_state, dialect).discard_result();
11281135
}).then([this, query, stream, &client_state, trace_state, dialect] () mutable {
11291136
tracing::trace(trace_state, "Done preparing on remote shards");
1130-
return _server._query_processor.local().prepare(std::move(query), client_state, dialect).then([this, stream, trace_state] (auto msg) {
1137+
return _server._query_processor.local().prepare(std::move(query), client_state, dialect).then([this, stream, &client_state, trace_state] (auto msg) {
11311138
tracing::trace(trace_state, "Done preparing on a local shard - preparing a result. ID is [{}]", seastar::value_of([&msg] {
11321139
return messages::result_message::prepared::cql::get_id(msg);
11331140
}));
1134-
return make_result(stream, *msg, trace_state, _version);
1141+
cql_metadata_id_wrapper metadata_id = is_metadata_id_supported(client_state)
1142+
? cql_metadata_id_wrapper(msg->get_metadata_id())
1143+
: cql_metadata_id_wrapper();
1144+
return make_result(stream, *msg, trace_state, _version, std::move(metadata_id));
11351145
});
11361146
});
11371147
}
@@ -1157,6 +1167,10 @@ process_execute_internal(service::client_state& client_state, distributed<cql3::
11571167
throw exceptions::prepared_query_not_found_exception(id);
11581168
}
11591169

1170+
cql_metadata_id_wrapper metadata_id = is_metadata_id_supported(client_state)
1171+
? cql_metadata_id_wrapper(cql3::cql_metadata_id_type(in.read_short_bytes()), prepared->get_metadata_id())
1172+
: cql_metadata_id_wrapper();
1173+
11601174
auto q_state = std::make_unique<cql_query_state>(client_state, trace_state, std::move(permit));
11611175
auto& query_state = q_state->query_state;
11621176
q_state->options = in.read_options(version, qp.local().get_cql_config());
@@ -1195,14 +1209,14 @@ process_execute_internal(service::client_state& client_state, distributed<cql3::
11951209

11961210
tracing::trace(trace_state, "Processing a statement");
11971211
return qp.local().execute_prepared_without_checking_exception_message(query_state, std::move(stmt), options, std::move(prepared), std::move(cache_key), needs_authorization)
1198-
.then([trace_state = query_state.get_trace_state(), skip_metadata, q_state = std::move(q_state), stream, version] (auto msg) {
1212+
.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 {
11991213
if (msg->move_to_shard()) {
12001214
return cql_server::process_fn_return_type(make_foreign(dynamic_pointer_cast<messages::result_message::bounce_to_shard>(msg)));
12011215
} else if (msg->is_exception()) {
12021216
return cql_server::process_fn_return_type(convert_error_message_to_coordinator_result(msg.get()));
12031217
} else {
12041218
tracing::trace(q_state->query_state.get_trace_state(), "Done processing - preparing a result");
1205-
return cql_server::process_fn_return_type(make_foreign(make_result(stream, *msg, q_state->query_state.get_trace_state(), version, skip_metadata)));
1219+
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)));
12061220
}
12071221
});
12081222
}
@@ -1323,7 +1337,8 @@ process_batch_internal(service::client_state& client_state, distributed<cql3::qu
13231337
return cql_server::process_fn_return_type(convert_error_message_to_coordinator_result(msg.get()));
13241338
} else {
13251339
tracing::trace(q_state->query_state.get_trace_state(), "Done processing - preparing a result");
1326-
return cql_server::process_fn_return_type(make_foreign(make_result(stream, *msg, trace_state, version)));
1340+
1341+
return cql_server::process_fn_return_type(make_foreign(make_result(stream, *msg, trace_state, version, cql_metadata_id_wrapper{})));
13271342
}
13281343
});
13291344
}
@@ -1538,11 +1553,13 @@ class cql_server::fmt_visitor : public messages::result_message::visitor_base {
15381553
uint8_t _version;
15391554
cql_server::response& _response;
15401555
bool _skip_metadata;
1556+
cql_metadata_id_wrapper _metadata_id;
15411557
public:
1542-
fmt_visitor(uint8_t version, cql_server::response& response, bool skip_metadata)
1558+
fmt_visitor(uint8_t version, cql_server::response& response, bool skip_metadata, cql_metadata_id_wrapper&& metadata_id)
15431559
: _version{version}
15441560
, _response{response}
15451561
, _skip_metadata{skip_metadata}
1562+
, _metadata_id(std::move(metadata_id))
15461563
{ }
15471564

15481565
virtual void visit(const messages::result_message::void_message&) override {
@@ -1557,8 +1574,11 @@ class cql_server::fmt_visitor : public messages::result_message::visitor_base {
15571574
virtual void visit(const messages::result_message::prepared::cql& m) override {
15581575
_response.write_int(0x0004);
15591576
_response.write_short_bytes(m.get_id());
1577+
if (_metadata_id.has_response_metadata_id()) {
1578+
_response.write_short_bytes(_metadata_id.get_response_metadata_id()._metadata_id);
1579+
}
15601580
_response.write(m.metadata(), _version);
1561-
_response.write(*m.result_metadata());
1581+
_response.write(*m.result_metadata(), _metadata_id);
15621582
}
15631583

15641584
virtual void visit(const messages::result_message::schema_change& m) override {
@@ -1578,7 +1598,7 @@ class cql_server::fmt_visitor : public messages::result_message::visitor_base {
15781598
virtual void visit(const messages::result_message::rows& m) override {
15791599
_response.write_int(0x0002);
15801600
auto& rs = m.rs();
1581-
_response.write(rs.get_metadata(), _skip_metadata);
1601+
_response.write(rs.get_metadata(), _metadata_id, _skip_metadata);
15821602
auto row_count_plhldr = _response.write_int_placeholder();
15831603

15841604
class visitor {
@@ -1606,7 +1626,7 @@ class cql_server::fmt_visitor : public messages::result_message::visitor_base {
16061626

16071627
std::unique_ptr<cql_server::response>
16081628
make_result(int16_t stream, messages::result_message& msg, const tracing::trace_state_ptr& tr_state,
1609-
cql_protocol_version_type version, bool skip_metadata) {
1629+
cql_protocol_version_type version, cql_metadata_id_wrapper&& metadata_id, bool skip_metadata) {
16101630
auto response = std::make_unique<cql_server::response>(stream, cql_binary_opcode::RESULT, tr_state);
16111631
if (__builtin_expect(!msg.warnings().empty() && version > 3, false)) {
16121632
response->set_frame_flag(cql_frame_flags::warning);
@@ -1616,7 +1636,7 @@ make_result(int16_t stream, messages::result_message& msg, const tracing::trace_
16161636
response->set_frame_flag(cql_frame_flags::custom_payload);
16171637
response->write_string_bytes_map(msg.custom_payload().value());
16181638
}
1619-
cql_server::fmt_visitor fmt{version, *response, skip_metadata};
1639+
cql_server::fmt_visitor fmt{version, *response, skip_metadata, std::move(metadata_id)};
16201640
msg.accept(fmt);
16211641
return response;
16221642
}
@@ -2024,7 +2044,7 @@ thread_local const type_codec::type_id_to_type_type type_codec::type_id_to_type
20242044
{ inet_addr_type, type_id::INET },
20252045
};
20262046

2027-
void cql_server::response::write(const cql3::metadata& m, bool no_metadata) {
2047+
void cql_server::response::write(const cql3::metadata& m, const cql_metadata_id_wrapper& metadata_id, bool no_metadata) {
20282048
auto flags = m.flags();
20292049
bool global_tables_spec = m.flags().contains<cql3::metadata::flag::GLOBAL_TABLES_SPEC>();
20302050
bool has_more_pages = m.flags().contains<cql3::metadata::flag::HAS_MORE_PAGES>();
@@ -2033,6 +2053,15 @@ void cql_server::response::write(const cql3::metadata& m, bool no_metadata) {
20332053
flags.set<cql3::metadata::flag::NO_METADATA>();
20342054
}
20352055

2056+
cql3::cql_metadata_id_type calculated_metadata_id{bytes{}};
2057+
if (metadata_id.has_request_metadata_id() && metadata_id.has_response_metadata_id()) {
2058+
if (metadata_id.get_request_metadata_id() != metadata_id.get_response_metadata_id()) {
2059+
flags.remove<cql3::metadata::flag::NO_METADATA>();
2060+
flags.set<cql3::metadata::flag::METADATA_CHANGED>();
2061+
no_metadata = false;
2062+
}
2063+
}
2064+
20362065
write_int(flags.mask());
20372066
write_int(m.column_count());
20382067

@@ -2044,6 +2073,10 @@ void cql_server::response::write(const cql3::metadata& m, bool no_metadata) {
20442073
return;
20452074
}
20462075

2076+
if (flags.contains<cql3::metadata::flag::METADATA_CHANGED>()) {
2077+
write_short_bytes(metadata_id.get_response_metadata_id()._metadata_id);
2078+
}
2079+
20472080
auto names_i = m.get_names().begin();
20482081

20492082
if (global_tables_spec) {
@@ -2096,6 +2129,28 @@ void cql_server::response::write(const cql3::prepared_metadata& m, uint8_t versi
20962129
}
20972130
}
20982131

2132+
bool cql_metadata_id_wrapper::has_request_metadata_id() const {
2133+
return _request_metadata_id.has_value();
2134+
}
2135+
2136+
bool cql_metadata_id_wrapper::has_response_metadata_id() const {
2137+
return _response_metadata_id.has_value();
2138+
}
2139+
2140+
const cql3::cql_metadata_id_type& cql_metadata_id_wrapper::get_request_metadata_id() const {
2141+
if (!has_request_metadata_id()) {
2142+
on_internal_error(clogger, "request metadata_id is empty");
2143+
}
2144+
return _request_metadata_id.value();
2145+
}
2146+
2147+
const cql3::cql_metadata_id_type& cql_metadata_id_wrapper::get_response_metadata_id() const {
2148+
if (!has_response_metadata_id()) {
2149+
on_internal_error(clogger, "response metadata_id is empty");
2150+
}
2151+
return _response_metadata_id.value();
2152+
}
2153+
20992154
future<utils::chunked_vector<client_data>> cql_server::get_client_data() {
21002155
utils::chunked_vector<client_data> ret;
21012156
co_await for_each_gently([&ret] (const generic_server::connection& c) {

0 commit comments

Comments
 (0)