Compare commits

..

1 Commits

Author SHA1 Message Date
Kefu Chai
2583a025fc s3/test: collect log on exit
the temporary directory holding the log file collecting the scylla
subprocess's output is specified by the test itself, and it is
`test_tempdir`. but unfortunately, cql-pytest/run.py is not aware
of this. so `cleanup_all()` is not able to print out the logging
messages at exit. as, please note, cql-pytest/run.py always
collect "log" file under the directory created using `pid_to_dir()`
where pid is the spawned subprocesses. but `object_store/run` uses
the main process's pid for its reusable tempdir.

so, with this change, we also register a cleanup func to printout
the logging message when the test exits.

Signed-off-by: Kefu Chai <kefu.chai@scylladb.com>

Closes #13647
2023-04-24 13:53:25 +03:00
539 changed files with 15781 additions and 17025 deletions

2
.gitmodules vendored
View File

@@ -1,6 +1,6 @@
[submodule "seastar"]
path = seastar
url = ../scylla-seastar
url = ../seastar
ignore = dirty
[submodule "swagger-ui"]
path = swagger-ui

View File

@@ -26,7 +26,6 @@ set(CMAKE_CXX_EXTENSIONS ON CACHE INTERNAL "")
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(Seastar_TESTING ON CACHE BOOL "" FORCE)
set(Seastar_API_LEVEL 6 CACHE STRING "" FORCE)
add_subdirectory(seastar)
# System libraries dependencies
@@ -184,25 +183,12 @@ target_link_libraries(scylla PRIVATE
# Force SHA1 build-id generation
set(default_linker_flags "-Wl,--build-id=sha1")
include(CheckLinkerFlag)
set(Scylla_USE_LINKER
""
CACHE
STRING
"Use specified linker instead of the default one")
if(Scylla_USE_LINKER)
set(linkers "${Scylla_USE_LINKER}")
else()
set(linkers "lld" "gold")
endif()
foreach(linker ${linkers})
foreach(linker "lld" "gold")
set(linker_flag "-fuse-ld=${linker}")
check_linker_flag(CXX ${linker_flag} "CXX_LINKER_HAVE_${linker}")
if(CXX_LINKER_HAVE_${linker})
string(APPEND default_linker_flags " ${linker_flag}")
break()
elseif(Scylla_USE_LINKER)
message(FATAL_ERROR "${Scylla_USE_LINKER} is not supported.")
endif()
endforeach()

View File

@@ -72,7 +72,7 @@ fi
# Default scylla product/version tags
PRODUCT=scylla
VERSION=5.3.0-rc1
VERSION=5.3.0-dev
if test -f version
then

View File

@@ -53,7 +53,7 @@ future<std::string> get_key_from_roles(service::storage_proxy& proxy, std::strin
if (result_set->empty()) {
co_await coroutine::return_exception(api_error::unrecognized_client(format("User not found: {}", username)));
}
const managed_bytes_opt& salted_hash = result_set->rows().front().front(); // We only asked for 1 row and 1 column
const bytes_opt& salted_hash = result_set->rows().front().front(); // We only asked for 1 row and 1 column
if (!salted_hash) {
co_await coroutine::return_exception(api_error::unrecognized_client(format("No password found for user: {}", username)));
}

View File

@@ -76,16 +76,13 @@ future<> controller::start_server() {
_ssg = create_smp_service_group(c).get0();
rmw_operation::set_default_write_isolation(_config.alternator_write_isolation());
executor::set_default_timeout(std::chrono::milliseconds(_config.alternator_timeout_in_ms()));
net::inet_address addr = utils::resolve(_config.alternator_address, family).get0();
auto get_cdc_metadata = [] (cdc::generation_service& svc) { return std::ref(svc.get_cdc_metadata()); };
auto get_timeout_in_ms = [] (const db::config& cfg) -> utils::updateable_value<uint32_t> {
return cfg.alternator_timeout_in_ms;
};
_executor.start(std::ref(_gossiper), std::ref(_proxy), std::ref(_mm), std::ref(_sys_dist_ks),
sharded_parameter(get_cdc_metadata, std::ref(_cdc_gen_svc)), _ssg.value(),
sharded_parameter(get_timeout_in_ms, std::ref(_config))).get();
_executor.start(std::ref(_gossiper), std::ref(_proxy), std::ref(_mm), std::ref(_sys_dist_ks), sharded_parameter(get_cdc_metadata, std::ref(_cdc_gen_svc)), _ssg.value()).get();
_server.start(std::ref(_executor), std::ref(_proxy), std::ref(_gossiper), std::ref(_auth_service), std::ref(_sl_controller)).get();
// Note: from this point on, if start_server() throws for any reason,
// it must first call stop_server() to stop the executor and server

View File

@@ -6,6 +6,8 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <regex>
#include "utils/base64.hh"
#include <seastar/core/sleep.hh>
@@ -88,20 +90,17 @@ json::json_return_type make_streamed(rjson::value&& value) {
// move objects to coroutine frame.
auto los = std::move(os);
auto lrs = std::move(rs);
std::exception_ptr ex;
try {
co_await rjson::print(*lrs, los);
co_await los.flush();
co_await los.close();
} catch (...) {
// at this point, we cannot really do anything. HTTP headers and return code are
// already written, and quite potentially a portion of the content data.
// just log + rethrow. It is probably better the HTTP server closes connection
// abruptly or something...
ex = std::current_exception();
elogger.error("Exception during streaming HTTP response: {}", ex);
}
co_await los.close();
if (ex) {
co_await coroutine::return_exception_ptr(std::move(ex));
elogger.error("Unhandled exception in data streaming: {}", std::current_exception());
throw;
}
co_return;
};
@@ -536,7 +535,7 @@ future<executor::request_return_type> executor::delete_table(client_state& clien
}
auto m = co_await mm.prepare_column_family_drop_announcement(keyspace_name, table_name, group0_guard.write_timestamp(), service::migration_manager::drop_views::yes);
auto m2 = co_await mm.prepare_keyspace_drop_announcement(keyspace_name, group0_guard.write_timestamp());
auto m2 = mm.prepare_keyspace_drop_announcement(keyspace_name, group0_guard.write_timestamp());
std::move(m2.begin(), m2.end(), std::back_inserter(m));
@@ -1366,11 +1365,14 @@ mutation put_or_delete_item::build(schema_ptr schema, api::timestamp_type ts) co
// The DynamoDB API doesn't let the client control the server's timeout, so
// we have a global default_timeout() for Alternator requests. The value of
// s_default_timeout_ms is overwritten in alternator::controller::start_server()
// s_default_timeout is overwritten in alternator::controller::start_server()
// based on the "alternator_timeout_in_ms" configuration parameter.
thread_local utils::updateable_value<uint32_t> executor::s_default_timeout_in_ms{10'000};
db::timeout_clock::duration executor::s_default_timeout = 10s;
void executor::set_default_timeout(db::timeout_clock::duration timeout) {
s_default_timeout = timeout;
}
db::timeout_clock::time_point executor::default_timeout() {
return db::timeout_clock::now() + std::chrono::milliseconds(s_default_timeout_in_ms);
return db::timeout_clock::now() + s_default_timeout;
}
static future<std::unique_ptr<rjson::value>> get_previous_item(
@@ -2298,14 +2300,14 @@ static std::optional<attrs_to_get> calculate_attrs_to_get(const rjson::value& re
* as before.
*/
void executor::describe_single_item(const cql3::selection::selection& selection,
const std::vector<managed_bytes_opt>& result_row,
const std::vector<bytes_opt>& result_row,
const std::optional<attrs_to_get>& attrs_to_get,
rjson::value& item,
bool include_all_embedded_attributes)
{
const auto& columns = selection.get_columns();
auto column_it = columns.begin();
for (const managed_bytes_opt& cell : result_row) {
for (const bytes_opt& cell : result_row) {
std::string column_name = (*column_it)->name_as_text();
if (cell && column_name != executor::ATTRS_COLUMN_NAME) {
if (!attrs_to_get || attrs_to_get->contains(column_name)) {
@@ -2313,9 +2315,7 @@ void executor::describe_single_item(const cql3::selection::selection& selection,
// so add() makes sense
rjson::add_with_string_name(item, column_name, rjson::empty_object());
rjson::value& field = item[column_name.c_str()];
cell->with_linearized([&] (bytes_view linearized_cell) {
rjson::add_with_string_name(field, type_to_string((*column_it)->type), json_key_column_value(linearized_cell, **column_it));
});
rjson::add_with_string_name(field, type_to_string((*column_it)->type), json_key_column_value(*cell, **column_it));
}
} else if (cell) {
auto deserialized = attrs_type()->deserialize(*cell);
@@ -2371,22 +2371,21 @@ std::optional<rjson::value> executor::describe_single_item(schema_ptr schema,
return item;
}
future<std::vector<rjson::value>> executor::describe_multi_item(schema_ptr schema,
const query::partition_slice&& slice,
shared_ptr<cql3::selection::selection> selection,
foreign_ptr<lw_shared_ptr<query::result>> query_result,
shared_ptr<const std::optional<attrs_to_get>> attrs_to_get) {
cql3::selection::result_set_builder builder(*selection, gc_clock::now());
query::result_view::consume(*query_result, slice, cql3::selection::result_set_builder::visitor(builder, *schema, *selection));
std::vector<rjson::value> executor::describe_multi_item(schema_ptr schema,
const query::partition_slice& slice,
const cql3::selection::selection& selection,
const query::result& query_result,
const std::optional<attrs_to_get>& attrs_to_get) {
cql3::selection::result_set_builder builder(selection, gc_clock::now());
query::result_view::consume(query_result, slice, cql3::selection::result_set_builder::visitor(builder, *schema, selection));
auto result_set = builder.build();
std::vector<rjson::value> ret;
for (auto& result_row : result_set->rows()) {
rjson::value item = rjson::empty_object();
describe_single_item(*selection, result_row, *attrs_to_get, item);
describe_single_item(selection, result_row, attrs_to_get, item);
ret.push_back(std::move(item));
co_await coroutine::maybe_yield();
}
co_return ret;
return ret;
}
static bool check_needs_read_before_write(const parsed::value& v) {
@@ -3258,7 +3257,8 @@ future<executor::request_return_type> executor::batch_get_item(client_state& cli
service::storage_proxy::coordinator_query_options(executor::default_timeout(), permit, client_state, trace_state)).then(
[schema = rs.schema, partition_slice = std::move(partition_slice), selection = std::move(selection), attrs_to_get = rs.attrs_to_get] (service::storage_proxy::coordinator_query_result qr) mutable {
utils::get_local_injector().inject("alternator_batch_get_item", [] { throw std::runtime_error("batch_get_item injection"); });
return describe_multi_item(std::move(schema), std::move(partition_slice), std::move(selection), std::move(qr.query_result), std::move(attrs_to_get));
std::vector<rjson::value> jsons = describe_multi_item(schema, partition_slice, *selection, *qr.query_result, *attrs_to_get);
return make_ready_future<std::vector<rjson::value>>(std::move(jsons));
});
response_futures.push_back(std::move(f));
}
@@ -3498,7 +3498,7 @@ public:
_column_it = _columns.begin();
}
void accept_value(managed_bytes_view_opt result_bytes_view) {
void accept_value(const std::optional<query::result_bytes_view>& result_bytes_view) {
if (!result_bytes_view) {
++_column_it;
return;

View File

@@ -22,7 +22,6 @@
#include "alternator/error.hh"
#include "stats.hh"
#include "utils/rjson.hh"
#include "utils/updateable_value.hh"
namespace db {
class system_distributed_keyspace;
@@ -171,16 +170,8 @@ public:
static constexpr auto KEYSPACE_NAME_PREFIX = "alternator_";
static constexpr std::string_view INTERNAL_TABLE_PREFIX = ".scylla.alternator.";
executor(gms::gossiper& gossiper,
service::storage_proxy& proxy,
service::migration_manager& mm,
db::system_distributed_keyspace& sdks,
cdc::metadata& cdc_metadata,
smp_service_group ssg,
utils::updateable_value<uint32_t> default_timeout_in_ms)
: _gossiper(gossiper), _proxy(proxy), _mm(mm), _sdks(sdks), _cdc_metadata(cdc_metadata), _ssg(ssg) {
s_default_timeout_in_ms = std::move(default_timeout_in_ms);
}
executor(gms::gossiper& gossiper, service::storage_proxy& proxy, service::migration_manager& mm, db::system_distributed_keyspace& sdks, cdc::metadata& cdc_metadata, smp_service_group ssg)
: _gossiper(gossiper), _proxy(proxy), _mm(mm), _sdks(sdks), _cdc_metadata(cdc_metadata), _ssg(ssg) {}
future<request_return_type> create_table(client_state& client_state, tracing::trace_state_ptr trace_state, service_permit permit, rjson::value request);
future<request_return_type> describe_table(client_state& client_state, tracing::trace_state_ptr trace_state, service_permit permit, rjson::value request);
@@ -208,16 +199,13 @@ public:
future<request_return_type> describe_continuous_backups(client_state& client_state, service_permit permit, rjson::value request);
future<> start();
future<> stop() {
// disconnect from the value source, but keep the value unchanged.
s_default_timeout_in_ms = utils::updateable_value<uint32_t>{s_default_timeout_in_ms()};
return make_ready_future<>();
}
future<> stop() { return make_ready_future<>(); }
static sstring table_name(const schema&);
static db::timeout_clock::time_point default_timeout();
static void set_default_timeout(db::timeout_clock::duration timeout);
private:
static thread_local utils::updateable_value<uint32_t> s_default_timeout_in_ms;
static db::timeout_clock::duration s_default_timeout;
public:
static schema_ptr find_table(service::storage_proxy&, const rjson::value& request);
@@ -234,14 +222,14 @@ public:
const query::result&,
const std::optional<attrs_to_get>&);
static future<std::vector<rjson::value>> describe_multi_item(schema_ptr schema,
const query::partition_slice&& slice,
shared_ptr<cql3::selection::selection> selection,
foreign_ptr<lw_shared_ptr<query::result>> query_result,
shared_ptr<const std::optional<attrs_to_get>> attrs_to_get);
static std::vector<rjson::value> describe_multi_item(schema_ptr schema,
const query::partition_slice& slice,
const cql3::selection::selection& selection,
const query::result& query_result,
const std::optional<attrs_to_get>& attrs_to_get);
static void describe_single_item(const cql3::selection::selection&,
const std::vector<managed_bytes_opt>&,
const std::vector<bytes_opt>&,
const std::optional<attrs_to_get>&,
rjson::value&,
bool = false);

View File

@@ -50,115 +50,6 @@ type_representation represent_type(alternator_type atype) {
return it->second;
}
// Get the magnitude and precision of a big_decimal - as these concepts are
// defined by DynamoDB - to allow us to enforce limits on those as explained
// in ssue #6794. The "magnitude" of 9e123 is 123 and of -9e-123 is -123,
// the "precision" of 12.34e56 is the number of significant digits - 4.
//
// Unfortunately it turned out to be quite difficult to take a big_decimal and
// calculate its magnitude and precision from its scale() and unscaled_value().
// So in the following ugly implementation we calculate them from the string
// representation instead. We assume the number was already parsed
// sucessfully to a big_decimal to it follows its syntax rules.
//
// FIXME: rewrite this function to take a big_decimal, not a string.
// Maybe a snippet like this can help:
// boost::multiprecision::cpp_int digits = boost::multiprecision::log10(num.unscaled_value().convert_to<boost::multiprecision::mpf_float_50>()).convert_to<boost::multiprecision::cpp_int>() + 1;
internal::magnitude_and_precision internal::get_magnitude_and_precision(std::string_view s) {
size_t e_or_end = s.find_first_of("eE");
std::string_view base = s.substr(0, e_or_end);
if (s[0]=='-' || s[0]=='+') {
base = base.substr(1);
}
int magnitude = 0;
int precision = 0;
size_t dot_or_end = base.find_first_of(".");
size_t nonzero = base.find_first_not_of("0");
if (dot_or_end != std::string_view::npos) {
if (nonzero == dot_or_end) {
// 0.000031 => magnitude = -5 (like 3.1e-5), precision = 2.
std::string_view fraction = base.substr(dot_or_end + 1);
size_t nonzero2 = fraction.find_first_not_of("0");
if (nonzero2 != std::string_view::npos) {
magnitude = -nonzero2 - 1;
precision = fraction.size() - nonzero2;
}
} else {
// 000123.45678 => magnitude = 2, precision = 8.
magnitude = dot_or_end - nonzero - 1;
precision = base.size() - nonzero - 1;
}
// trailing zeros don't count to precision, e.g., precision
// of 1000.0, 1.0 or 1.0000 are just 1.
size_t last_significant = base.find_last_not_of(".0");
if (last_significant == std::string_view::npos) {
precision = 0;
} else if (last_significant < dot_or_end) {
// e.g., 1000.00 reduce 5 = 7 - (0+1) - 1 from precision
precision -= base.size() - last_significant - 2;
} else {
// e.g., 1235.60 reduce 5 = 7 - (5+1) from precision
precision -= base.size() - last_significant - 1;
}
} else if (nonzero == std::string_view::npos) {
// all-zero integer 000000
magnitude = 0;
precision = 0;
} else {
magnitude = base.size() - 1 - nonzero;
precision = base.size() - nonzero;
// trailing zeros don't count to precision, e.g., precision
// of 1000 is just 1.
size_t last_significant = base.find_last_not_of("0");
if (last_significant == std::string_view::npos) {
precision = 0;
} else {
// e.g., 1000 reduce 3 = 4 - (0+1)
precision -= base.size() - last_significant - 1;
}
}
if (precision && e_or_end != std::string_view::npos) {
std::string_view exponent = s.substr(e_or_end + 1);
if (exponent.size() > 4) {
// don't even bother atoi(), exponent is too large
magnitude = exponent[0]=='-' ? -9999 : 9999;
} else {
try {
magnitude += boost::lexical_cast<int32_t>(exponent);
} catch (...) {
magnitude = 9999;
}
}
}
return magnitude_and_precision {magnitude, precision};
}
// Parse a number read from user input, validating that it has a valid
// numeric format and also in the allowed magnitude and precision ranges
// (see issue #6794). Throws an api_error::validation if the validation
// failed.
static big_decimal parse_and_validate_number(std::string_view s) {
try {
big_decimal ret(s);
auto [magnitude, precision] = internal::get_magnitude_and_precision(s);
if (magnitude > 125) {
throw api_error::validation(format("Number overflow: {}. Attempting to store a number with magnitude larger than supported range.", s));
}
if (magnitude < -130) {
throw api_error::validation(format("Number underflow: {}. Attempting to store a number with magnitude lower than supported range.", s));
}
if (precision > 38) {
throw api_error::validation(format("Number too precise: {}. Attempting to store a number with more significant digits than supported.", s));
}
return ret;
} catch (const marshal_exception& e) {
throw api_error::validation(format("The parameter cannot be converted to a numeric value: {}", s));
}
}
struct from_json_visitor {
const rjson::value& v;
bytes_ostream& bo;
@@ -176,7 +67,11 @@ struct from_json_visitor {
bo.write(boolean_type->decompose(v.GetBool()));
}
void operator()(const decimal_type_impl& t) const {
bo.write(decimal_type->decompose(parse_and_validate_number(rjson::to_string_view(v))));
try {
bo.write(t.from_string(rjson::to_string_view(v)));
} catch (const marshal_exception& e) {
throw api_error::validation(format("The parameter cannot be converted to a numeric value: {}", v));
}
}
// default
void operator()(const abstract_type& t) const {
@@ -308,8 +203,6 @@ bytes get_key_from_typed_value(const rjson::value& key_typed_value, const column
// FIXME: it's difficult at this point to get information if value was provided
// in request or comes from the storage, for now we assume it's user's fault.
return *unwrap_bytes(value, true);
} else if (column.type == decimal_type) {
return decimal_type->decompose(parse_and_validate_number(rjson::to_string_view(value)));
} else {
return column.type->from_string(value_view);
}
@@ -402,13 +295,16 @@ big_decimal unwrap_number(const rjson::value& v, std::string_view diagnostic) {
if (it->name != "N") {
throw api_error::validation(format("{}: expected number, found type '{}'", diagnostic, it->name));
}
if (!it->value.IsString()) {
// We shouldn't reach here. Callers normally validate their input
// earlier with validate_value().
throw api_error::validation(format("{}: improperly formatted number constant", diagnostic));
try {
if (!it->value.IsString()) {
// We shouldn't reach here. Callers normally validate their input
// earlier with validate_value().
throw api_error::validation(format("{}: improperly formatted number constant", diagnostic));
}
return big_decimal(rjson::to_string_view(it->value));
} catch (const marshal_exception& e) {
throw api_error::validation(format("The parameter cannot be converted to a numeric value: {}", it->value));
}
big_decimal ret = parse_and_validate_number(rjson::to_string_view(it->value));
return ret;
}
std::optional<big_decimal> try_unwrap_number(const rjson::value& v) {
@@ -420,8 +316,8 @@ std::optional<big_decimal> try_unwrap_number(const rjson::value& v) {
return std::nullopt;
}
try {
return parse_and_validate_number(rjson::to_string_view(it->value));
} catch (api_error&) {
return big_decimal(rjson::to_string_view(it->value));
} catch (const marshal_exception& e) {
return std::nullopt;
}
}

View File

@@ -94,12 +94,5 @@ std::optional<rjson::value> set_diff(const rjson::value& v1, const rjson::value&
// Returns a null value if one of the arguments is not actually a list.
rjson::value list_concatenate(const rjson::value& v1, const rjson::value& v2);
namespace internal {
struct magnitude_and_precision {
int magnitude;
int precision;
};
magnitude_and_precision get_magnitude_and_precision(std::string_view);
}
}

View File

@@ -241,7 +241,7 @@ static bool is_expired(const rjson::value& expiration_time, gc_clock::time_point
// understands it is an expiration event - not a user-initiated deletion.
static future<> expire_item(service::storage_proxy& proxy,
const service::query_state& qs,
const std::vector<managed_bytes_opt>& row,
const std::vector<bytes_opt>& row,
schema_ptr schema,
api::timestamp_type ts) {
// Prepare the row key to delete
@@ -260,7 +260,7 @@ static future<> expire_item(service::storage_proxy& proxy,
// FIXME: log or increment a metric if this happens.
return make_ready_future<>();
}
exploded_pk.push_back(to_bytes(*row_c));
exploded_pk.push_back(*row_c);
}
auto pk = partition_key::from_exploded(exploded_pk);
mutation m(schema, pk);
@@ -280,7 +280,7 @@ static future<> expire_item(service::storage_proxy& proxy,
// FIXME: log or increment a metric if this happens.
return make_ready_future<>();
}
exploded_ck.push_back(to_bytes(*row_c));
exploded_ck.push_back(*row_c);
}
auto ck = clustering_key::from_exploded(exploded_ck);
m.partition().clustered_row(*schema, ck).apply(tombstone(ts, gc_clock::now()));
@@ -387,7 +387,7 @@ class token_ranges_owned_by_this_shard {
class ranges_holder_primary {
const dht::token_range_vector _token_ranges;
public:
ranges_holder_primary(const locator::vnode_effective_replication_map_ptr& erm, gms::gossiper& g, gms::inet_address ep)
ranges_holder_primary(const locator::effective_replication_map_ptr& erm, gms::gossiper& g, gms::inet_address ep)
: _token_ranges(erm->get_primary_ranges(ep)) {}
std::size_t size() const { return _token_ranges.size(); }
const dht::token_range& operator[](std::size_t i) const {
@@ -593,7 +593,7 @@ static future<> scan_table_ranges(
continue;
}
for (const auto& row : rows) {
const managed_bytes_opt& cell = row[*expiration_column];
const bytes_opt& cell = row[*expiration_column];
if (!cell) {
continue;
}

View File

@@ -437,68 +437,6 @@
}
]
},
{
"path":"/column_family/tombstone_gc/{name}",
"operations":[
{
"method":"GET",
"summary":"Check if tombstone GC is enabled for a given table",
"type":"boolean",
"nickname":"get_tombstone_gc",
"produces":[
"application/json"
],
"parameters":[
{
"name":"name",
"description":"The table name in keyspace:name format",
"required":true,
"allowMultiple":false,
"type":"string",
"paramType":"path"
}
]
},
{
"method":"POST",
"summary":"Enable tombstone GC for a given table",
"type":"void",
"nickname":"enable_tombstone_gc",
"produces":[
"application/json"
],
"parameters":[
{
"name":"name",
"description":"The table name in keyspace:name format",
"required":true,
"allowMultiple":false,
"type":"string",
"paramType":"path"
}
]
},
{
"method":"DELETE",
"summary":"Disable tombstone GC for a given table",
"type":"void",
"nickname":"disable_tombstone_gc",
"produces":[
"application/json"
],
"parameters":[
{
"name":"name",
"description":"The table name in keyspace:name format",
"required":true,
"allowMultiple":false,
"type":"string",
"paramType":"path"
}
]
}
]
},
{
"path":"/column_family/estimate_keys/{name}",
"operations":[

View File

@@ -2110,65 +2110,6 @@
}
]
},
{
"path":"/storage_service/tombstone_gc/{keyspace}",
"operations":[
{
"method":"POST",
"summary":"Enable tombstone GC",
"type":"void",
"nickname":"enable_tombstone_gc",
"produces":[
"application/json"
],
"parameters":[
{
"name":"keyspace",
"description":"The keyspace",
"required":true,
"allowMultiple":false,
"type":"string",
"paramType":"path"
},
{
"name":"cf",
"description":"Comma-separated column family names",
"required":false,
"allowMultiple":false,
"type":"string",
"paramType":"query"
}
]
},
{
"method":"DELETE",
"summary":"Disable tombstone GC",
"type":"void",
"nickname":"disable_tombstone_gc",
"produces":[
"application/json"
],
"parameters":[
{
"name":"keyspace",
"description":"The keyspace",
"required":true,
"allowMultiple":false,
"type":"string",
"paramType":"path"
},
{
"name":"cf",
"description":"Comma-separated column family names",
"required":false,
"allowMultiple":false,
"type":"string",
"paramType":"query"
}
]
}
]
},
{
"path":"/storage_service/deliver_hints",
"operations":[
@@ -2690,7 +2631,7 @@
"description":"File creation time"
},
"generation":{
"type":"string",
"type":"long",
"description":"SSTable generation"
},
"level":{

View File

@@ -871,7 +871,6 @@ void set_column_family(http_context& ctx, routes& r, sharded<db::system_keyspace
});
cf::enable_auto_compaction.set(r, [&ctx](std::unique_ptr<http::request> req) {
apilog.info("column_family/enable_auto_compaction: name={}", req->param["name"]);
return ctx.db.invoke_on(0, [&ctx, req = std::move(req)] (replica::database& db) {
auto g = replica::database::autocompaction_toggle_guard(db);
return foreach_column_family(ctx, req->param["name"], [](replica::column_family &cf) {
@@ -883,7 +882,6 @@ void set_column_family(http_context& ctx, routes& r, sharded<db::system_keyspace
});
cf::disable_auto_compaction.set(r, [&ctx](std::unique_ptr<http::request> req) {
apilog.info("column_family/disable_auto_compaction: name={}", req->param["name"]);
return ctx.db.invoke_on(0, [&ctx, req = std::move(req)] (replica::database& db) {
auto g = replica::database::autocompaction_toggle_guard(db);
return foreach_column_family(ctx, req->param["name"], [](replica::column_family &cf) {
@@ -894,30 +892,6 @@ void set_column_family(http_context& ctx, routes& r, sharded<db::system_keyspace
});
});
cf::get_tombstone_gc.set(r, [&ctx] (const_req req) {
auto uuid = get_uuid(req.param["name"], ctx.db.local());
replica::table& t = ctx.db.local().find_column_family(uuid);
return t.tombstone_gc_enabled();
});
cf::enable_tombstone_gc.set(r, [&ctx](std::unique_ptr<http::request> req) {
apilog.info("column_family/enable_tombstone_gc: name={}", req->param["name"]);
return foreach_column_family(ctx, req->param["name"], [](replica::table& t) {
t.set_tombstone_gc_enabled(true);
}).then([] {
return make_ready_future<json::json_return_type>(json_void());
});
});
cf::disable_tombstone_gc.set(r, [&ctx](std::unique_ptr<http::request> req) {
apilog.info("column_family/disable_tombstone_gc: name={}", req->param["name"]);
return foreach_column_family(ctx, req->param["name"], [](replica::table& t) {
t.set_tombstone_gc_enabled(false);
}).then([] {
return make_ready_future<json::json_return_type>(json_void());
});
});
cf::get_built_indexes.set(r, [&ctx, &sys_ks](std::unique_ptr<http::request> req) {
auto ks_cf = parse_fully_qualified_cf_name(req->param["name"]);
auto&& ks = std::get<0>(ks_cf);
@@ -981,7 +955,6 @@ void set_column_family(http_context& ctx, routes& r, sharded<db::system_keyspace
cf::set_compaction_strategy_class.set(r, [&ctx](std::unique_ptr<http::request> req) {
sstring strategy = req->get_query_param("class_name");
apilog.info("column_family/set_compaction_strategy_class: name={} strategy={}", req->param["name"], strategy);
return foreach_column_family(ctx, req->param["name"], [strategy](replica::column_family& cf) {
cf.set_compaction_strategy(sstables::compaction_strategy::type(strategy));
}).then([] {
@@ -1050,7 +1023,6 @@ void set_column_family(http_context& ctx, routes& r, sharded<db::system_keyspace
fail(unimplemented::cause::API);
}
apilog.info("column_family/force_major_compaction: name={}", req->param["name"]);
auto [ks, cf] = parse_fully_qualified_cf_name(req->param["name"]);
auto keyspace = validate_keyspace(ctx, ks);
std::vector<table_id> table_infos = {ctx.db.local().find_uuid(ks, cf)};

View File

@@ -220,47 +220,32 @@ seastar::future<json::json_return_type> run_toppartitions_query(db::toppartition
});
}
static future<json::json_return_type> set_tables(http_context& ctx, const sstring& keyspace, std::vector<sstring> tables, std::function<future<>(replica::table&)> set) {
future<json::json_return_type> set_tables_autocompaction(http_context& ctx, const sstring &keyspace, std::vector<sstring> tables, bool enabled) {
if (tables.empty()) {
tables = map_keys(ctx.db.local().find_keyspace(keyspace).metadata().get()->cf_meta_data());
}
return do_with(keyspace, std::move(tables), [&ctx, set] (const sstring& keyspace, const std::vector<sstring>& tables) {
return ctx.db.invoke_on_all([&keyspace, &tables, set] (replica::database& db) {
return parallel_for_each(tables, [&db, &keyspace, set] (const sstring& table) {
replica::table& t = db.find_column_family(keyspace, table);
return set(t);
});
apilog.info("set_tables_autocompaction: enabled={} keyspace={} tables={}", enabled, keyspace, tables);
return do_with(keyspace, std::move(tables), [&ctx, enabled] (const sstring &keyspace, const std::vector<sstring>& tables) {
return ctx.db.invoke_on(0, [&ctx, &keyspace, &tables, enabled] (replica::database& db) {
auto g = replica::database::autocompaction_toggle_guard(db);
return ctx.db.invoke_on_all([&keyspace, &tables, enabled] (replica::database& db) {
return parallel_for_each(tables, [&db, &keyspace, enabled] (const sstring& table) {
replica::column_family& cf = db.find_column_family(keyspace, table);
if (enabled) {
cf.enable_auto_compaction();
} else {
return cf.disable_auto_compaction();
}
return make_ready_future<>();
});
}).finally([g = std::move(g)] {});
});
}).then([] {
return make_ready_future<json::json_return_type>(json_void());
});
}
future<json::json_return_type> set_tables_autocompaction(http_context& ctx, const sstring &keyspace, std::vector<sstring> tables, bool enabled) {
apilog.info("set_tables_autocompaction: enabled={} keyspace={} tables={}", enabled, keyspace, tables);
return ctx.db.invoke_on(0, [&ctx, keyspace, tables = std::move(tables), enabled] (replica::database& db) {
auto g = replica::database::autocompaction_toggle_guard(db);
return set_tables(ctx, keyspace, tables, [enabled] (replica::table& cf) {
if (enabled) {
cf.enable_auto_compaction();
} else {
return cf.disable_auto_compaction();
}
return make_ready_future<>();
}).finally([g = std::move(g)] {});
});
}
future<json::json_return_type> set_tables_tombstone_gc(http_context& ctx, const sstring &keyspace, std::vector<sstring> tables, bool enabled) {
apilog.info("set_tables_tombstone_gc: enabled={} keyspace={} tables={}", enabled, keyspace, tables);
return set_tables(ctx, keyspace, std::move(tables), [enabled] (replica::table& t) {
t.set_tombstone_gc_enabled(enabled);
return make_ready_future<>();
});
}
void set_transport_controller(http_context& ctx, routes& r, cql_transport::controller& ctl) {
ss::start_native_transport.set(r, [&ctl](std::unique_ptr<http::request> req) {
return smp::submit_to(0, [&] {
@@ -634,7 +619,7 @@ void set_storage_service(http_context& ctx, routes& r, sharded<service::storage_
ss::describe_any_ring.set(r, [&ctx, &ss](std::unique_ptr<http::request> req) {
// Find an arbitrary non-system keyspace.
auto keyspaces = ctx.db.local().get_non_local_vnode_based_strategy_keyspaces();
auto keyspaces = ctx.db.local().get_non_local_strategy_keyspaces();
if (keyspaces.empty()) {
throw std::runtime_error("No keyspace provided and no non system kespace exist");
}
@@ -1126,22 +1111,6 @@ void set_storage_service(http_context& ctx, routes& r, sharded<service::storage_
return set_tables_autocompaction(ctx, keyspace, tables, false);
});
ss::enable_tombstone_gc.set(r, [&ctx](std::unique_ptr<http::request> req) {
auto keyspace = validate_keyspace(ctx, req->param);
auto tables = parse_tables(keyspace, ctx, req->query_parameters, "cf");
apilog.info("enable_tombstone_gc: keyspace={} tables={}", keyspace, tables);
return set_tables_tombstone_gc(ctx, keyspace, tables, true);
});
ss::disable_tombstone_gc.set(r, [&ctx](std::unique_ptr<http::request> req) {
auto keyspace = validate_keyspace(ctx, req->param);
auto tables = parse_tables(keyspace, ctx, req->query_parameters, "cf");
apilog.info("disable_tombstone_gc: keyspace={} tables={}", keyspace, tables);
return set_tables_tombstone_gc(ctx, keyspace, tables, false);
});
ss::deliver_hints.set(r, [](std::unique_ptr<http::request> req) {
//TBD
unimplemented();
@@ -1288,7 +1257,7 @@ void set_storage_service(http_context& ctx, routes& r, sharded<service::storage_
ss::sstable info;
info.timestamp = t;
info.generation = fmt::to_string(sstable->generation());
info.generation = sstables::generation_value(sstable->generation());
info.level = sstable->get_sstable_level();
info.size = sstable->bytes_on_disk();
info.data_size = sstable->ondisk_data_size();
@@ -1525,12 +1494,27 @@ void set_snapshot(http_context& ctx, routes& r, sharded<db::snapshot_ctl>& snap_
throw httpd::bad_param_exception(fmt::format("Unknown argument for 'quarantine_mode' parameter: {}", quarantine_mode_str));
}
sstables::compaction_stats stats;
auto& compaction_module = db.local().get_compaction_manager().get_task_manager_module();
auto task = co_await compaction_module.make_and_start_task<scrub_sstables_compaction_task_impl>({}, std::move(keyspace), db, column_families, opts, stats);
const auto& reduce_compaction_stats = [] (const compaction_manager::compaction_stats_opt& lhs, const compaction_manager::compaction_stats_opt& rhs) {
sstables::compaction_stats stats{};
stats += lhs.value();
stats += rhs.value();
return stats;
};
try {
co_await task->done();
if (stats.validation_errors) {
auto opt_stats = co_await db.map_reduce0([&] (replica::database& db) {
return map_reduce(column_families, [&] (sstring cfname) -> future<std::optional<sstables::compaction_stats>> {
auto& cm = db.get_compaction_manager();
auto& cf = db.find_column_family(keyspace, cfname);
sstables::compaction_stats stats{};
co_await cf.parallel_foreach_table_state([&] (compaction::table_state& ts) mutable -> future<> {
auto r = co_await cm.perform_sstable_scrub(ts, opts);
stats += r.value_or(sstables::compaction_stats{});
});
co_return stats;
}, std::make_optional(sstables::compaction_stats{}), reduce_compaction_stats);
}, std::make_optional(sstables::compaction_stats{}), reduce_compaction_stats);
if (opt_stats && opt_stats->validation_errors) {
co_return json::json_return_type(static_cast<int>(scrub_status::validation_errors));
}
} catch (const sstables::compaction_aborted_exception&) {

View File

@@ -35,9 +35,16 @@ public:
///
authenticated_user() = default;
explicit authenticated_user(std::string_view name);
friend bool operator==(const authenticated_user&, const authenticated_user&) noexcept = default;
};
inline bool operator==(const authenticated_user& u1, const authenticated_user& u2) noexcept {
return u1.name == u2.name;
}
inline bool operator!=(const authenticated_user& u1, const authenticated_user& u2) noexcept {
return !(u1 == u2);
}
const authenticated_user& anonymous_user() noexcept;
inline bool is_anonymous(const authenticated_user& u) noexcept {

View File

@@ -39,6 +39,10 @@ inline bool operator==(const permission_details& pd1, const permission_details&
== std::forward_as_tuple(pd2.role_name, pd2.resource, pd2.permissions.mask());
}
inline bool operator!=(const permission_details& pd1, const permission_details& pd2) {
return !(pd1 == pd2);
}
inline bool operator<(const permission_details& pd1, const permission_details& pd2) {
return std::forward_as_tuple(pd1.role_name, pd1.resource, pd1.permissions)
< std::forward_as_tuple(pd2.role_name, pd2.resource, pd2.permissions);

View File

@@ -79,13 +79,6 @@ static permission_set applicable_permissions(const service_level_resource_view &
}
static permission_set applicable_permissions(const functions_resource_view& fv) {
if (fv.function_name() || fv.function_signature()) {
return permission_set::of<
permission::ALTER,
permission::DROP,
permission::AUTHORIZE,
permission::EXECUTE>();
}
return permission_set::of<
permission::CREATE,
permission::ALTER,
@@ -299,7 +292,7 @@ std::optional<std::vector<std::string_view>> functions_resource_view::function_a
std::vector<std::string_view> parts;
if (_resource._parts[3] == "") {
return parts;
return {};
}
for (size_t i = 3; i < _resource._parts.size(); i++) {
parts.push_back(_resource._parts[i]);

View File

@@ -117,12 +117,20 @@ private:
friend class functions_resource_view;
friend bool operator<(const resource&, const resource&);
friend bool operator==(const resource&, const resource&) = default;
friend bool operator==(const resource&, const resource&);
friend resource parse_resource(std::string_view);
};
bool operator<(const resource&, const resource&);
inline bool operator==(const resource& r1, const resource& r2) {
return (r1._kind == r2._kind) && (r1._parts == r2._parts);
}
inline bool operator!=(const resource& r1, const resource& r2) {
return !(r1 == r2);
}
std::ostream& operator<<(std::ostream&, const resource&);
class resource_kind_mismatch : public std::invalid_argument {

View File

@@ -17,6 +17,10 @@ std::ostream& operator<<(std::ostream& os, const role_or_anonymous& mr) {
return os;
}
bool operator==(const role_or_anonymous& mr1, const role_or_anonymous& mr2) noexcept {
return mr1.name == mr2.name;
}
bool is_anonymous(const role_or_anonymous& mr) noexcept {
return !mr.name.has_value();
}

View File

@@ -26,11 +26,16 @@ public:
role_or_anonymous() = default;
role_or_anonymous(std::string_view name) : name(name) {
}
friend bool operator==(const role_or_anonymous&, const role_or_anonymous&) noexcept = default;
};
std::ostream& operator<<(std::ostream&, const role_or_anonymous&);
bool operator==(const role_or_anonymous&, const role_or_anonymous&) noexcept;
inline bool operator!=(const role_or_anonymous& mr1, const role_or_anonymous& mr2) noexcept {
return !(mr1 == mr2);
}
bool is_anonymous(const role_or_anonymous&) noexcept;
}

View File

@@ -55,7 +55,6 @@ future<bool> default_role_row_satisfies(
return qp.execute_internal(
query,
db::consistency_level::ONE,
internal_distributed_query_state(),
{meta::DEFAULT_SUPERUSER_NAME},
cql3::query_processor::cache_internal::yes).then([&qp, &p](::shared_ptr<cql3::untyped_result_set> results) {
if (results->empty()) {

View File

@@ -7,7 +7,6 @@
*/
#include <seastar/core/coroutine.hh>
#include "auth/resource.hh"
#include "auth/service.hh"
#include <algorithm>
@@ -21,7 +20,6 @@
#include "auth/allow_all_authorizer.hh"
#include "auth/common.hh"
#include "auth/role_or_anonymous.hh"
#include "cql3/functions/function_name.hh"
#include "cql3/functions/functions.hh"
#include "cql3/query_processor.hh"
#include "cql3/untyped_result_set.hh"
@@ -68,7 +66,6 @@ private:
void on_update_function(const sstring& ks_name, const sstring& function_name) override {}
void on_update_aggregate(const sstring& ks_name, const sstring& aggregate_name) override {}
void on_update_view(const sstring& ks_name, const sstring& view_name, bool columns_changed) override {}
void on_update_tablet_metadata() override {}
void on_drop_keyspace(const sstring& ks_name) override {
// Do it in the background.
@@ -78,12 +75,6 @@ private:
}).handle_exception([] (std::exception_ptr e) {
log.error("Unexpected exception while revoking all permissions on dropped keyspace: {}", e);
});
(void)_authorizer.revoke_all(
auth::make_functions_resource(ks_name)).handle_exception_type([](const unsupported_authorization_operation&) {
// Nothing.
}).handle_exception([] (std::exception_ptr e) {
log.error("Unexpected exception while revoking all permissions on functions in dropped keyspace: {}", e);
});
}
void on_drop_column_family(const sstring& ks_name, const sstring& cf_name) override {
@@ -98,22 +89,8 @@ private:
}
void on_drop_user_type(const sstring& ks_name, const sstring& type_name) override {}
void on_drop_function(const sstring& ks_name, const sstring& function_name) override {
(void)_authorizer.revoke_all(
auth::make_functions_resource(ks_name, function_name)).handle_exception_type([](const unsupported_authorization_operation&) {
// Nothing.
}).handle_exception([] (std::exception_ptr e) {
log.error("Unexpected exception while revoking all permissions on dropped function: {}", e);
});
}
void on_drop_aggregate(const sstring& ks_name, const sstring& aggregate_name) override {
(void)_authorizer.revoke_all(
auth::make_functions_resource(ks_name, aggregate_name)).handle_exception_type([](const unsupported_authorization_operation&) {
// Nothing.
}).handle_exception([] (std::exception_ptr e) {
log.error("Unexpected exception while revoking all permissions on dropped aggregate: {}", e);
});
}
void on_drop_function(const sstring& ks_name, const sstring& function_name) override {}
void on_drop_aggregate(const sstring& ks_name, const sstring& aggregate_name) override {}
void on_drop_view(const sstring& ks_name, const sstring& view_name) override {}
};

View File

@@ -17,7 +17,7 @@
#include <functional>
#include <compare>
#include "utils/mutable_view.hh"
#include "utils/simple_hashers.hh"
#include <xxhash.h>
using bytes = basic_sstring<int8_t, uint32_t, 31, false>;
using bytes_view = std::basic_string_view<int8_t>;
@@ -160,7 +160,18 @@ struct appending_hash<bytes_view> {
}
};
using bytes_view_hasher = simple_xx_hasher;
struct bytes_view_hasher : public hasher {
XXH64_state_t _state;
bytes_view_hasher(uint64_t seed = 0) noexcept {
XXH64_reset(&_state, seed);
}
void update(const char* ptr, size_t length) noexcept {
XXH64_update(&_state, ptr, length);
}
size_t finalize() {
return static_cast<size_t>(XXH64_digest(&_state));
}
};
namespace std {
template <>

View File

@@ -53,10 +53,6 @@ public:
using difference_type = std::ptrdiff_t;
using pointer = bytes_view*;
using reference = bytes_view&;
struct implementation {
blob_storage* current_chunk;
};
private:
chunk* _current = nullptr;
public:
@@ -79,11 +75,11 @@ public:
++(*this);
return tmp;
}
bool operator==(const fragment_iterator&) const = default;
implementation extract_implementation() const {
return implementation {
.current_chunk = _current,
};
bool operator==(const fragment_iterator& other) const {
return _current == other._current;
}
bool operator!=(const fragment_iterator& other) const {
return _current != other._current;
}
};
using const_iterator = fragment_iterator;
@@ -436,6 +432,10 @@ public:
return true;
}
bool operator!=(const bytes_ostream& other) const {
return !(*this == other);
}
// Makes this instance empty.
//
// The first buffer is not deallocated, so callers may rely on the

View File

@@ -68,6 +68,7 @@ public:
_pos = -1;
}
bool operator==(const iterator& o) const { return _pos == o._pos; }
bool operator!=(const iterator& o) const { return _pos != o._pos; }
};
public:
cartesian_product(const std::vector<std::vector<T>>& vec_of_vecs) : _vec_of_vecs(vec_of_vecs) {}

View File

@@ -65,6 +65,7 @@ public:
void ttl(int v) { _ttl = v; }
bool operator==(const options& o) const;
bool operator!=(const options& o) const;
};
} // namespace cdc

View File

@@ -1090,8 +1090,19 @@ shared_ptr<db::system_distributed_keyspace> generation_service::get_sys_dist_ks(
return _sys_dist_ks.local_shared();
}
std::ostream& operator<<(std::ostream& os, const generation_id& gen_id) {
std::visit(make_visitor(
[&os] (const generation_id_v1& id) { os << id.ts; },
[&os] (const generation_id_v2& id) { os << "(" << id.ts << ", " << id.id << ")"; }
), gen_id);
return os;
}
db_clock::time_point get_ts(const generation_id& gen_id) {
return std::visit([] (auto& id) { return id.ts; }, gen_id);
return std::visit(make_visitor(
[] (const generation_id_v1& id) { return id.ts; },
[] (const generation_id_v2& id) { return id.ts; }
), gen_id);
}
} // namespace cdc

View File

@@ -28,35 +28,7 @@ struct generation_id_v2 {
using generation_id = std::variant<generation_id_v1, generation_id_v2>;
std::ostream& operator<<(std::ostream&, const generation_id&);
db_clock::time_point get_ts(const generation_id&);
} // namespace cdc
template <>
struct fmt::formatter<cdc::generation_id_v1> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const cdc::generation_id_v1& gen_id, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "{}", gen_id.ts);
}
};
template <>
struct fmt::formatter<cdc::generation_id_v2> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const cdc::generation_id_v2& gen_id, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "({}, {})", gen_id.ts, gen_id.id);
}
};
template <>
struct fmt::formatter<cdc::generation_id> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const cdc::generation_id& gen_id, FormatContext& ctx) const {
return std::visit([&ctx] (auto& id) {
return fmt::format_to(ctx.out(), "{}", id);
}, gen_id);
}
};

View File

@@ -395,6 +395,9 @@ bool cdc::options::operator==(const options& o) const {
return enabled() == o.enabled() && _preimage == o._preimage && _postimage == o._postimage && _ttl == o._ttl
&& _delta_mode == o._delta_mode;
}
bool cdc::options::operator!=(const options& o) const {
return !(*this == o);
}
namespace cdc {
@@ -632,6 +635,9 @@ public:
bool operator==(const collection_iterator& x) const {
return _v == x._v;
}
bool operator!=(const collection_iterator& x) const {
return !(*this == x);
}
private:
void next() {
--_rem;

View File

@@ -389,7 +389,7 @@ struct extract_changes_visitor {
}
void partition_delete(const tombstone& t) {
_result[t.timestamp].partition_deletions = partition_deletion{t};
_result[t.timestamp].partition_deletions = {t};
}
constexpr bool finished() const { return false; }

View File

@@ -93,6 +93,9 @@ public:
bool operator==(const iterator& other) const {
return _position == other._position;
}
bool operator!=(const iterator& other) const {
return !(*this == other);
}
};
public:
explicit partition_cells_range(const mutation_partition& mp) : _mp(mp) { }

View File

@@ -15,6 +15,12 @@
std::atomic<int64_t> clocks_offset;
std::ostream& operator<<(std::ostream& os, db_clock::time_point tp) {
auto t = db_clock::to_time_t(tp);
::tm t_buf;
return os << std::put_time(::gmtime_r(&t, &t_buf), "%Y/%m/%d %T");
}
std::string format_timestamp(api::timestamp_type ts) {
auto t = std::time_t(std::chrono::duration_cast<std::chrono::seconds>(api::timestamp_clock::duration(ts)).count());
::tm t_buf;

View File

@@ -75,7 +75,8 @@ public:
const interval::interval_type& iv = *_i;
return position_range{iv.lower().position(), iv.upper().position()};
}
bool operator==(const position_range_iterator& other) const = default;
bool operator==(const position_range_iterator& other) const { return _i == other._i; }
bool operator!=(const position_range_iterator& other) const { return _i != other._i; }
position_range_iterator& operator++() {
++_i;
return *this;

View File

@@ -1,7 +1,9 @@
set(disabled_warnings
c++11-narrowing
mismatched-tags
missing-braces
overloaded-virtual
parentheses-equality
unsupported-friend)
include(CheckCXXCompilerFlag)
foreach(warning ${disabled_warnings})
@@ -11,11 +13,7 @@ foreach(warning ${disabled_warnings})
endif()
endforeach()
list(TRANSFORM _supported_warnings PREPEND "-Wno-")
string(JOIN " " CMAKE_CXX_FLAGS
"-Wall"
"-Werror"
"-Wno-error=deprecated-declarations"
${_supported_warnings})
string(JOIN " " CMAKE_CXX_FLAGS "-Wall" "-Werror" ${_supported_warnings})
function(default_target_arch arch)
set(x86_instruction_sets i386 i686 x86_64)

View File

@@ -168,11 +168,7 @@ std::ostream& operator<<(std::ostream& os, pretty_printed_throughput tp) {
}
static api::timestamp_type get_max_purgeable_timestamp(const table_state& table_s, sstable_set::incremental_selector& selector,
const std::unordered_set<shared_sstable>& compacting_set, const dht::decorated_key& dk, uint64_t& bloom_filter_checks) {
if (!table_s.tombstone_gc_enabled()) [[unlikely]] {
return api::min_timestamp;
}
const std::unordered_set<shared_sstable>& compacting_set, const dht::decorated_key& dk) {
auto timestamp = table_s.min_memtable_timestamp();
std::optional<utils::hashed_key> hk;
for (auto&& sst : boost::range::join(selector.select(dk).sstables, table_s.compacted_undeleted_sstables())) {
@@ -183,7 +179,6 @@ static api::timestamp_type get_max_purgeable_timestamp(const table_state& table_
hk = sstables::sstable::make_hashed_key(*table_s.schema(), dk.key());
}
if (sst->filter_has_key(*hk)) {
bloom_filter_checks++;
timestamp = std::min(timestamp, sst->get_stats_metadata().min_timestamp);
}
}
@@ -419,12 +414,9 @@ private:
class formatted_sstables_list {
bool _include_origin = true;
std::vector<std::string> _ssts;
std::vector<sstring> _ssts;
public:
formatted_sstables_list() = default;
void reserve(size_t n) {
_ssts.reserve(n);
}
explicit formatted_sstables_list(const std::vector<shared_sstable>& ssts, bool include_origin) : _include_origin(include_origin) {
_ssts.reserve(ssts.size());
for (const auto& sst : ssts) {
@@ -439,7 +431,9 @@ public:
};
std::ostream& operator<<(std::ostream& os, const formatted_sstables_list& lst) {
fmt::print(os, "[{}]", fmt::join(lst._ssts, ","));
os << "[";
os << boost::algorithm::join(lst._ssts, ",");
os << "]";
return os;
}
@@ -464,7 +458,6 @@ protected:
uint64_t _start_size = 0;
uint64_t _end_size = 0;
uint64_t _estimated_partitions = 0;
uint64_t _bloom_filter_checks = 0;
db::replay_position _rp;
encoding_stats_collector _stats_collector;
bool _can_split_large_partition = false;
@@ -578,7 +571,7 @@ protected:
// Tombstone expiration is enabled based on the presence of sstable set.
// If it's not present, we cannot purge tombstones without the risk of resurrecting data.
bool tombstone_expiration_enabled() const {
return bool(_sstable_set) && _table_s.tombstone_gc_enabled();
return bool(_sstable_set);
}
compaction_writer create_gc_compaction_writer() const {
@@ -632,6 +625,11 @@ protected:
flat_mutation_reader_v2::filter make_partition_filter() const {
return [this] (const dht::decorated_key& dk) {
#ifdef SEASTAR_DEBUG
// sstables should never be shared with other shards at this point.
assert(dht::shard_of(*_schema, dk.token()) == this_shard_id());
#endif
if (!_owned_ranges_checker->belongs_to_current_node(dk.token())) {
log_trace("Token {} does not belong to this node, skipping", dk.token());
return false;
@@ -670,7 +668,6 @@ private:
future<> setup() {
auto ssts = make_lw_shared<sstables::sstable_set>(make_sstable_set_for_input());
formatted_sstables_list formatted_msg;
formatted_msg.reserve(_sstables.size());
auto fully_expired = _table_s.fully_expired_sstables(_sstables, gc_clock::now());
min_max_tracker<api::timestamp_type> timestamp_tracker;
@@ -787,7 +784,6 @@ protected:
.ended_at = ended_at,
.start_size = _start_size,
.end_size = _end_size,
.bloom_filter_checks = _bloom_filter_checks,
},
};
@@ -828,7 +824,7 @@ private:
};
}
return [this] (const dht::decorated_key& dk) {
return get_max_purgeable_timestamp(_table_s, *_selector, _compacting_for_max_purgeable_func, dk, _bloom_filter_checks);
return get_max_purgeable_timestamp(_table_s, *_selector, _compacting_for_max_purgeable_func, dk);
};
}
@@ -1245,8 +1241,62 @@ public:
class scrub_compaction final : public regular_compaction {
public:
static void report_validation_error(compaction_type type, const ::schema& schema, sstring what, std::string_view action = "") {
clogger.error("[{} compaction {}.{}] {}{}{}", type, schema.ks_name(), schema.cf_name(), what, action.empty() ? "" : "; ", action);
static void report_invalid_partition(compaction_type type, mutation_fragment_stream_validator& validator, const dht::decorated_key& new_key,
std::string_view action = "") {
const auto& schema = validator.schema();
const auto& current_key = validator.previous_partition_key();
clogger.error("[{} compaction {}.{}] Invalid partition {} ({}), partition is out-of-order compared to previous partition {} ({}){}{}",
type,
schema.ks_name(),
schema.cf_name(),
new_key.key().with_schema(schema),
new_key,
current_key.key().with_schema(schema),
current_key,
action.empty() ? "" : "; ",
action);
}
static void report_invalid_partition_start(compaction_type type, mutation_fragment_stream_validator& validator, const dht::decorated_key& new_key,
std::string_view action = "") {
const auto& schema = validator.schema();
const auto& current_key = validator.previous_partition_key();
clogger.error("[{} compaction {}.{}] Invalid partition start for partition {} ({}), previous partition {} ({}) didn't end with a partition-end fragment{}{}",
type,
schema.ks_name(),
schema.cf_name(),
new_key.key().with_schema(schema),
new_key,
current_key.key().with_schema(schema),
current_key,
action.empty() ? "" : "; ",
action);
}
static void report_invalid_mutation_fragment(compaction_type type, mutation_fragment_stream_validator& validator, const mutation_fragment_v2& mf,
std::string_view action = "") {
const auto& schema = validator.schema();
const auto& key = validator.previous_partition_key();
const auto prev_pos = validator.previous_position();
clogger.error("[{} compaction {}.{}] Invalid {} fragment{} ({}) in partition {} ({}),"
" fragment is out-of-order compared to previous {} fragment{} ({}){}{}",
type,
schema.ks_name(),
schema.cf_name(),
mf.mutation_fragment_kind(),
mf.has_key() ? format(" with key {}", mf.key().with_schema(schema)) : "",
mf.position(),
key.key().with_schema(schema),
key,
prev_pos.region(),
prev_pos.has_key() ? format(" with key {}", prev_pos.key().with_schema(schema)) : "",
prev_pos,
action.empty() ? "" : "; ",
action);
}
static void report_invalid_end_of_stream(compaction_type type, mutation_fragment_stream_validator& validator, std::string_view action = "") {
const auto& schema = validator.schema();
const auto& key = validator.previous_partition_key();
clogger.error("[{} compaction {}.{}] Invalid end-of-stream, last partition {} ({}) didn't end with a partition-end fragment{}{}",
type, schema.ks_name(), schema.cf_name(), key.key().with_schema(schema), key, action.empty() ? "" : "; ", action);
}
private:
@@ -1269,9 +1319,9 @@ private:
++_validation_errors;
}
void on_unexpected_partition_start(const mutation_fragment_v2& ps, sstring error) {
auto report_fn = [this, error] (std::string_view action = "") {
report_validation_error(compaction_type::Scrub, *_schema, error, action);
void on_unexpected_partition_start(const mutation_fragment_v2& ps) {
auto report_fn = [this, &ps] (std::string_view action = "") {
report_invalid_partition_start(compaction_type::Scrub, _validator, ps.as_partition_start().key(), action);
};
maybe_abort_scrub(report_fn);
report_fn("Rectifying by adding assumed missing partition-end");
@@ -1293,9 +1343,9 @@ private:
}
}
skip on_invalid_partition(const dht::decorated_key& new_key, sstring error) {
auto report_fn = [this, error] (std::string_view action = "") {
report_validation_error(compaction_type::Scrub, *_schema, error, action);
skip on_invalid_partition(const dht::decorated_key& new_key) {
auto report_fn = [this, &new_key] (std::string_view action = "") {
report_invalid_partition(compaction_type::Scrub, _validator, new_key, action);
};
maybe_abort_scrub(report_fn);
if (_scrub_mode == compaction_type_options::scrub::mode::segregate) {
@@ -1309,9 +1359,9 @@ private:
return skip::yes;
}
skip on_invalid_mutation_fragment(const mutation_fragment_v2& mf, sstring error) {
auto report_fn = [this, error] (std::string_view action = "") {
report_validation_error(compaction_type::Scrub, *_schema, error, action);
skip on_invalid_mutation_fragment(const mutation_fragment_v2& mf) {
auto report_fn = [this, &mf] (std::string_view action = "") {
report_invalid_mutation_fragment(compaction_type::Scrub, _validator, mf, "");
};
maybe_abort_scrub(report_fn);
@@ -1346,9 +1396,9 @@ private:
return skip::yes;
}
void on_invalid_end_of_stream(sstring error) {
auto report_fn = [this, error] (std::string_view action = "") {
report_validation_error(compaction_type::Scrub, *_schema, error, action);
void on_invalid_end_of_stream() {
auto report_fn = [this] (std::string_view action = "") {
report_invalid_end_of_stream(compaction_type::Scrub, _validator, action);
};
maybe_abort_scrub(report_fn);
// Handle missing partition_end
@@ -1367,27 +1417,21 @@ private:
// and shouldn't be verified. We know the last fragment the
// validator saw is a partition-start, passing it another one
// will confuse it.
if (!_skip_to_next_partition) {
if (auto res = _validator(mf); !res) {
on_unexpected_partition_start(mf, res.what());
}
if (!_skip_to_next_partition && !_validator(mf)) {
on_unexpected_partition_start(mf);
// Continue processing this partition start.
}
_skip_to_next_partition = false;
// Then check that the partition monotonicity stands.
const auto& dk = mf.as_partition_start().key();
if (auto res = _validator(dk); !res) {
if (on_invalid_partition(dk, res.what()) == skip::yes) {
continue;
}
if (!_validator(dk) && on_invalid_partition(dk) == skip::yes) {
continue;
}
} else if (_skip_to_next_partition) {
continue;
} else {
if (auto res = _validator(mf); !res) {
if (on_invalid_mutation_fragment(mf, res.what()) == skip::yes) {
continue;
}
if (!_validator(mf) && on_invalid_mutation_fragment(mf) == skip::yes) {
continue;
}
}
push_mutation_fragment(std::move(mf));
@@ -1396,8 +1440,8 @@ private:
_end_of_stream = _reader.is_end_of_stream() && _reader.is_buffer_empty();
if (_end_of_stream) {
if (auto res = _validator.on_end_of_stream(); !res) {
on_invalid_end_of_stream(res.what());
if (!_validator.on_end_of_stream()) {
on_invalid_end_of_stream();
}
}
}
@@ -1678,29 +1722,81 @@ static std::unique_ptr<compaction> make_compaction(table_state& table_s, sstable
return descriptor.options.visit(visitor_factory);
}
static future<compaction_result> scrub_sstables_validate_mode(sstables::compaction_descriptor descriptor, compaction_data& cdata, table_state& table_s) {
auto schema = table_s.schema();
auto permit = table_s.make_compaction_reader_permit();
future<uint64_t> scrub_validate_mode_validate_reader(flat_mutation_reader_v2 reader, const compaction_data& cdata) {
auto schema = reader.schema();
uint64_t validation_errors = 0;
uint64_t errors = 0;
std::exception_ptr ex;
for (const auto& sst : descriptor.sstables) {
clogger.info("Scrubbing in validate mode {}", sst->get_filename());
try {
auto validator = mutation_fragment_stream_validator(*schema);
validation_errors += co_await sst->validate(permit, descriptor.io_priority, cdata.abort, [&schema] (sstring what) {
scrub_compaction::report_validation_error(compaction_type::Scrub, *schema, what);
});
// Did validation actually finish because aborted?
if (cdata.is_stop_requested()) {
// Compaction manager will catch this exception and re-schedule the compaction.
throw compaction_stopped_exception(schema->ks_name(), schema->cf_name(), cdata.stop_requested);
while (auto mf_opt = co_await reader()) {
if (cdata.is_stop_requested()) [[unlikely]] {
// Compaction manager will catch this exception and re-schedule the compaction.
throw compaction_stopped_exception(schema->ks_name(), schema->cf_name(), cdata.stop_requested);
}
const auto& mf = *mf_opt;
if (mf.is_partition_start()) {
const auto& ps = mf.as_partition_start();
if (!validator(mf)) {
scrub_compaction::report_invalid_partition_start(compaction_type::Scrub, validator, ps.key());
validator.reset(mf);
++errors;
}
if (!validator(ps.key())) {
scrub_compaction::report_invalid_partition(compaction_type::Scrub, validator, ps.key());
validator.reset(ps.key());
++errors;
}
} else {
if (!validator(mf)) {
scrub_compaction::report_invalid_mutation_fragment(compaction_type::Scrub, validator, mf);
validator.reset(mf);
++errors;
}
}
}
clogger.info("Finished scrubbing in validate mode {} - sstable is {}", sst->get_filename(), validation_errors == 0 ? "valid" : "invalid");
if (!validator.on_end_of_stream()) {
scrub_compaction::report_invalid_end_of_stream(compaction_type::Scrub, validator);
++errors;
}
} catch (...) {
ex = std::current_exception();
}
co_await reader.close();
if (ex) {
co_return coroutine::exception(std::move(ex));
}
co_return errors;
}
static future<compaction_result> scrub_sstables_validate_mode(sstables::compaction_descriptor descriptor, compaction_data& cdata, table_state& table_s) {
auto schema = table_s.schema();
formatted_sstables_list sstables_list_msg;
auto sstables = make_lw_shared<sstables::sstable_set>(sstables::make_partitioned_sstable_set(schema, false));
for (const auto& sst : descriptor.sstables) {
sstables_list_msg += sst;
sstables->insert(sst);
}
clogger.info("Scrubbing in validate mode {}", sstables_list_msg);
auto permit = table_s.make_compaction_reader_permit();
auto reader = sstables->make_crawling_reader(schema, permit, descriptor.io_priority, nullptr);
const auto validation_errors = co_await scrub_validate_mode_validate_reader(std::move(reader), cdata);
clogger.info("Finished scrubbing in validate mode {} - sstable(s) are {}", sstables_list_msg, validation_errors == 0 ? "valid" : "invalid");
if (validation_errors != 0) {
for (auto& sst : descriptor.sstables) {
for (auto& sst : *sstables->all()) {
co_await sst->change_state(sstables::quarantine_dir);
}
}

View File

@@ -92,15 +92,12 @@ struct compaction_stats {
uint64_t start_size = 0;
uint64_t end_size = 0;
uint64_t validation_errors = 0;
// Bloom filter checks during max purgeable calculation
uint64_t bloom_filter_checks = 0;
compaction_stats& operator+=(const compaction_stats& r) {
ended_at = std::max(ended_at, r.ended_at);
start_size += r.start_size;
end_size += r.end_size;
validation_errors += r.validation_errors;
bloom_filter_checks += r.bloom_filter_checks;
return *this;
}
friend compaction_stats operator+(const compaction_stats& l, const compaction_stats& r) {
@@ -133,4 +130,7 @@ get_fully_expired_sstables(const table_state& table_s, const std::vector<sstable
// For tests, can drop after we virtualize sstables.
flat_mutation_reader_v2 make_scrubbing_reader(flat_mutation_reader_v2 rd, compaction_type_options::scrub::mode scrub_mode, uint64_t& validation_errors);
// For tests, can drop after we virtualize sstables.
future<uint64_t> scrub_validate_mode_validate_reader(flat_mutation_reader_v2 rd, const compaction_data& info);
}

View File

@@ -453,7 +453,7 @@ protected:
};
setup_new_compaction(descriptor.run_identifier);
cmlog.info0("User initiated compaction started on behalf of {}", *t);
cmlog.info0("User initiated compaction started on behalf of {}.{}", t->schema()->ks_name(), t->schema()->cf_name());
// Now that the sstables for major compaction are registered
// and the user_initiated_backlog_tracker is set up
@@ -533,8 +533,8 @@ compaction_manager::compaction_reenabler::compaction_reenabler(compaction_manage
, _holder(_compaction_state.gate.hold())
{
_compaction_state.compaction_disabled_counter++;
cmlog.debug("Temporarily disabled compaction for {}. compaction_disabled_counter={}",
t, _compaction_state.compaction_disabled_counter);
cmlog.debug("Temporarily disabled compaction for {}.{}. compaction_disabled_counter={}",
_table->schema()->ks_name(), _table->schema()->cf_name(), _compaction_state.compaction_disabled_counter);
}
compaction_manager::compaction_reenabler::compaction_reenabler(compaction_reenabler&& o) noexcept
@@ -547,12 +547,13 @@ compaction_manager::compaction_reenabler::compaction_reenabler(compaction_reenab
compaction_manager::compaction_reenabler::~compaction_reenabler() {
// submit compaction request if we're the last holder of the gate which is still opened.
if (_table && --_compaction_state.compaction_disabled_counter == 0 && !_compaction_state.gate.is_closed()) {
cmlog.debug("Reenabling compaction for {}", *_table);
cmlog.debug("Reenabling compaction for {}.{}",
_table->schema()->ks_name(), _table->schema()->cf_name());
try {
_cm.submit(*_table);
} catch (...) {
cmlog.warn("compaction_reenabler could not reenable compaction for {}: {}",
*_table, std::current_exception());
cmlog.warn("compaction_reenabler could not reenable compaction for {}.{}: {}",
_table->schema()->ks_name(), _table->schema()->cf_name(), std::current_exception());
}
}
}
@@ -605,7 +606,8 @@ compaction::compaction_state::~compaction_state() {
std::string compaction_task_executor::describe() const {
auto* t = _compacting_table;
return fmt::format("{} task {} for table {} [{}]", _description, fmt::ptr(this), *t, fmt::ptr(t));
auto s = t->schema();
return fmt::format("{} task {} for table {}.{} [{}]", _description, fmt::ptr(this), s->ks_name(), s->cf_name(), fmt::ptr(t));
}
compaction_task_executor::~compaction_task_executor() {
@@ -842,7 +844,8 @@ future<> compaction_manager::postponed_compactions_reevaluation() {
if (!_compaction_state.contains(t)) {
continue;
}
cmlog.debug("resubmitting postponed compaction for table {} [{}]", *t, fmt::ptr(t));
auto s = t->schema();
cmlog.debug("resubmitting postponed compaction for table {}.{} [{}]", s->ks_name(), s->cf_name(), fmt::ptr(t));
submit(*t);
co_await coroutine::maybe_yield();
}
@@ -891,7 +894,7 @@ future<> compaction_manager::stop_ongoing_compactions(sstring reason, table_stat
if (cmlog.is_enabled(level)) {
std::string scope = "";
if (t) {
scope = fmt::format(" for table {}", *t);
scope = fmt::format(" for table {}.{}", t->schema()->ks_name(), t->schema()->cf_name());
}
if (type_opt) {
scope += fmt::format(" {} type={}", scope.size() ? "and" : "for", *type_opt);
@@ -1034,8 +1037,8 @@ protected:
co_return std::nullopt;
}
if (!_cm.can_register_compaction(t, weight, descriptor.fan_in())) {
cmlog.debug("Refused compaction job ({} sstable(s)) of weight {} for {}, postponing it...",
descriptor.sstables.size(), weight, t);
cmlog.debug("Refused compaction job ({} sstable(s)) of weight {} for {}.{}, postponing it...",
descriptor.sstables.size(), weight, t.schema()->ks_name(), t.schema()->cf_name());
switch_state(state::postponed);
_cm.postpone_compaction_for_table(&t);
co_return std::nullopt;
@@ -1045,8 +1048,8 @@ protected:
auto release_exhausted = [&compacting] (const std::vector<sstables::shared_sstable>& exhausted_sstables) {
compacting.release_compacting(exhausted_sstables);
};
cmlog.debug("Accepted compaction job: task={} ({} sstable(s)) of weight {} for {}",
fmt::ptr(this), descriptor.sstables.size(), weight, t);
cmlog.debug("Accepted compaction job: task={} ({} sstable(s)) of weight {} for {}.{}",
fmt::ptr(this), descriptor.sstables.size(), weight, t.schema()->ks_name(), t.schema()->cf_name());
setup_new_compaction(descriptor.run_identifier);
std::exception_ptr ex;
@@ -1106,7 +1109,8 @@ bool compaction_manager::can_perform_regular_compaction(table_state& t) {
future<> compaction_manager::maybe_wait_for_sstable_count_reduction(table_state& t) {
auto schema = t.schema();
if (!can_perform_regular_compaction(t)) {
cmlog.trace("maybe_wait_for_sstable_count_reduction in {}: cannot perform regular compaction", t);
cmlog.trace("maybe_wait_for_sstable_count_reduction in {}.{}: cannot perform regular compaction",
schema->ks_name(), schema->cf_name());
co_return;
}
auto num_runs_for_compaction = [&, this] {
@@ -1119,8 +1123,8 @@ future<> compaction_manager::maybe_wait_for_sstable_count_reduction(table_state&
const auto threshold = size_t(std::max(schema->max_compaction_threshold(), 32));
auto count = num_runs_for_compaction();
if (count <= threshold) {
cmlog.trace("No need to wait for sstable count reduction in {}: {} <= {}",
t, count, threshold);
cmlog.trace("No need to wait for sstable count reduction in {}.{}: {} <= {}",
schema->ks_name(), schema->cf_name(), count, threshold);
co_return;
}
// Reduce the chances of falling into an endless wait, if compaction
@@ -1138,8 +1142,8 @@ future<> compaction_manager::maybe_wait_for_sstable_count_reduction(table_state&
}
auto end = db_clock::now();
auto elapsed_ms = (end - start) / 1ms;
cmlog.warn("Waited {}ms for compaction of {} to catch up on {} sstable runs",
elapsed_ms, t, count);
cmlog.warn("Waited {}ms for compaction of {}.{} to catch up on {} sstable runs",
elapsed_ms, schema->ks_name(), schema->cf_name(), count);
}
namespace compaction {
@@ -1260,16 +1264,12 @@ protected:
std::exception_ptr ex;
try {
table_state& t = *_compacting_table;
auto size = t.maintenance_sstable_set().size();
if (!size) {
cmlog.debug("Skipping off-strategy compaction for {}, No candidates were found", t);
finish_compaction();
co_return std::nullopt;
}
cmlog.info("Starting off-strategy compaction for {}, {} candidates were found", t, size);
auto maintenance_sstables = t.maintenance_sstable_set().all();
cmlog.info("Starting off-strategy compaction for {}.{}, {} candidates were found",
t.schema()->ks_name(), t.schema()->cf_name(), maintenance_sstables->size());
co_await run_offstrategy_compaction(_compaction_data);
finish_compaction();
cmlog.info("Done with off-strategy compaction for {}", t);
cmlog.info("Done with off-strategy compaction for {}.{}", t.schema()->ks_name(), t.schema()->cf_name());
co_return std::nullopt;
} catch (...) {
ex = std::current_exception();
@@ -1524,18 +1524,14 @@ protected:
co_return std::nullopt;
}
private:
// Releases reference to cleaned files such that respective used disk space can be freed.
void release_exhausted(std::vector<sstables::shared_sstable> exhausted_sstables) {
_compacting.release_compacting(exhausted_sstables);
}
future<> run_cleanup_job(sstables::compaction_descriptor descriptor) {
co_await coroutine::switch_to(_cm.compaction_sg().cpu);
// Releases reference to cleaned files such that respective used disk space can be freed.
auto release_exhausted = [this, &descriptor] (std::vector<sstables::shared_sstable> exhausted_sstables) mutable {
auto exhausted = boost::copy_range<std::unordered_set<sstables::shared_sstable>>(exhausted_sstables);
std::erase_if(descriptor.sstables, [&] (const sstables::shared_sstable& sst) {
return exhausted.contains(sst);
});
_compacting.release_compacting(exhausted_sstables);
};
for (;;) {
compaction_backlog_tracker user_initiated(std::make_unique<user_initiated_backlog_tracker>(_cm._compaction_controller.backlog_of_shares(200), _cm.available_memory()));
_cm.register_backlog_tracker(user_initiated);
@@ -1543,7 +1539,8 @@ private:
std::exception_ptr ex;
try {
setup_new_compaction(descriptor.run_identifier);
co_await compact_sstables_and_update_history(descriptor, _compaction_data, release_exhausted);
co_await compact_sstables_and_update_history(descriptor, _compaction_data,
std::bind(&cleanup_sstables_compaction_task_executor::release_exhausted, this, std::placeholders::_1));
finish_compaction();
_cm.reevaluate_postponed_compactions();
co_return; // done with current job
@@ -1564,11 +1561,6 @@ private:
bool needs_cleanup(const sstables::shared_sstable& sst,
const dht::token_range_vector& sorted_owned_ranges) {
// Finish early if the keyspace has no owned token ranges (in this data center)
if (sorted_owned_ranges.empty()) {
return true;
}
auto first_token = sst->get_first_decorated_key().token();
auto last_token = sst->get_last_decorated_key().token();
dht::token_range sst_token_range = dht::token_range::make(first_token, last_token);
@@ -1588,13 +1580,9 @@ bool needs_cleanup(const sstables::shared_sstable& sst,
return true;
}
bool compaction_manager::update_sstable_cleanup_state(table_state& t, const sstables::shared_sstable& sst, const dht::token_range_vector& sorted_owned_ranges) {
bool compaction_manager::update_sstable_cleanup_state(table_state& t, const sstables::shared_sstable& sst, owned_ranges_ptr owned_ranges_ptr) {
auto& cs = get_compaction_state(&t);
if (sst->is_shared()) {
throw std::runtime_error(format("Shared SSTable {} cannot be marked as requiring cleanup, as it can only be processed by resharding",
sst->get_filename()));
}
if (needs_cleanup(sst, sorted_owned_ranges)) {
if (owned_ranges_ptr && needs_cleanup(sst, *owned_ranges_ptr)) {
cs.sstables_requiring_cleanup.insert(sst);
return true;
} else {
@@ -1603,97 +1591,46 @@ bool compaction_manager::update_sstable_cleanup_state(table_state& t, const ssta
}
}
bool compaction_manager::erase_sstable_cleanup_state(table_state& t, const sstables::shared_sstable& sst) {
auto& cs = get_compaction_state(&t);
return cs.sstables_requiring_cleanup.erase(sst);
}
bool compaction_manager::requires_cleanup(table_state& t, const sstables::shared_sstable& sst) const {
const auto& cs = get_compaction_state(&t);
return cs.sstables_requiring_cleanup.contains(sst);
}
future<> compaction_manager::perform_cleanup(owned_ranges_ptr sorted_owned_ranges, table_state& t) {
constexpr auto sleep_duration = std::chrono::seconds(10);
constexpr auto max_idle_duration = std::chrono::seconds(300);
auto& cs = get_compaction_state(&t);
co_await try_perform_cleanup(sorted_owned_ranges, t);
auto last_idle = seastar::lowres_clock::now();
while (!cs.sstables_requiring_cleanup.empty()) {
auto idle = seastar::lowres_clock::now() - last_idle;
if (idle >= max_idle_duration) {
auto msg = ::format("Cleanup timed out after {} seconds of no progress", std::chrono::duration_cast<std::chrono::seconds>(idle).count());
cmlog.warn("{}", msg);
co_await coroutine::return_exception(std::runtime_error(msg));
}
auto has_sstables_eligible_for_compaction = [&] {
for (auto& sst : cs.sstables_requiring_cleanup) {
if (sstables::is_eligible_for_compaction(sst)) {
return true;
}
}
return false;
};
cmlog.debug("perform_cleanup: waiting for sstables to become eligible for cleanup");
co_await t.get_staging_done_condition().when(sleep_duration, [&] { return has_sstables_eligible_for_compaction(); });
if (!has_sstables_eligible_for_compaction()) {
continue;
}
co_await try_perform_cleanup(sorted_owned_ranges, t);
last_idle = seastar::lowres_clock::now();
}
}
future<> compaction_manager::try_perform_cleanup(owned_ranges_ptr sorted_owned_ranges, table_state& t) {
auto check_for_cleanup = [this, &t] {
return boost::algorithm::any_of(_tasks, [&t] (auto& task) {
return task->compacting_table() == &t && task->type() == sstables::compaction_type::Cleanup;
});
};
if (check_for_cleanup()) {
throw std::runtime_error(format("cleanup request failed: there is an ongoing cleanup on {}", t));
throw std::runtime_error(format("cleanup request failed: there is an ongoing cleanup on {}.{}",
t.schema()->ks_name(), t.schema()->cf_name()));
}
co_await run_with_compaction_disabled(t, [&] () -> future<> {
auto update_sstables_cleanup_state = [&] (const sstables::sstable_set& set) -> future<> {
// Hold on to the sstable set since it may be overwritten
// while we yield in this loop.
auto set_holder = set.shared_from_this();
co_await set.for_each_sstable_gently([&] (const sstables::shared_sstable& sst) {
update_sstable_cleanup_state(t, sst, *sorted_owned_ranges);
});
};
co_await update_sstables_cleanup_state(t.main_sstable_set());
co_await update_sstables_cleanup_state(t.maintenance_sstable_set());
});
auto& cs = get_compaction_state(&t);
if (cs.sstables_requiring_cleanup.empty()) {
cmlog.debug("perform_cleanup for {} found no sstables requiring cleanup", t);
co_return;
}
// Some sstables may remain in sstables_requiring_cleanup
// for later processing if they can't be cleaned up right now.
// They are erased from sstables_requiring_cleanup by compacting.release_compacting
cs.owned_ranges_ptr = std::move(sorted_owned_ranges);
auto found_maintenance_sstables = bool(t.maintenance_sstable_set().for_each_sstable_until([this, &t] (const sstables::shared_sstable& sst) {
return stop_iteration(requires_cleanup(t, sst));
}));
if (found_maintenance_sstables) {
co_await perform_offstrategy(t);
if (sorted_owned_ranges->empty()) {
throw std::runtime_error("cleanup request failed: sorted_owned_ranges is empty");
}
// Called with compaction_disabled
auto get_sstables = [this, &t] () -> future<std::vector<sstables::shared_sstable>> {
auto& cs = get_compaction_state(&t);
co_return get_candidates(t, cs.sstables_requiring_cleanup);
auto get_sstables = [this, &t, sorted_owned_ranges] () -> future<std::vector<sstables::shared_sstable>> {
return seastar::async([this, &t, sorted_owned_ranges = std::move(sorted_owned_ranges)] {
auto update_sstables_cleanup_state = [&] (const sstables::sstable_set& set) {
set.for_each_sstable([&] (const sstables::shared_sstable& sst) {
update_sstable_cleanup_state(t, sst, sorted_owned_ranges);
seastar::thread::maybe_yield();
});
};
update_sstables_cleanup_state(t.main_sstable_set());
update_sstables_cleanup_state(t.maintenance_sstable_set());
// Some sstables may remain in sstables_requiring_cleanup
// for later processing if they can't be cleaned up right now.
// They are erased from sstables_requiring_cleanup by compacting.release_compacting
auto& cs = get_compaction_state(&t);
if (!cs.sstables_requiring_cleanup.empty()) {
cs.owned_ranges_ptr = std::move(sorted_owned_ranges);
}
return get_candidates(t, cs.sstables_requiring_cleanup);
});
};
co_await perform_task_on_all_files<cleanup_sstables_compaction_task_executor>(t, sstables::compaction_type_options::make_cleanup(), std::move(sorted_owned_ranges),
@@ -1764,7 +1701,8 @@ compaction::compaction_state::compaction_state(table_state& t)
void compaction_manager::add(table_state& t) {
auto [_, inserted] = _compaction_state.try_emplace(&t, t);
if (!inserted) {
on_internal_error(cmlog, format("compaction_state for table {} [{}] already exists", t, fmt::ptr(&t)));
auto s = t.schema();
on_internal_error(cmlog, format("compaction_state for table {}.{} [{}] already exists", s->ks_name(), s->cf_name(), fmt::ptr(&t)));
}
}

View File

@@ -304,12 +304,7 @@ public:
// given sstable, e.g. after node loses part of its token range because
// of a newly added node.
future<> perform_cleanup(owned_ranges_ptr sorted_owned_ranges, compaction::table_state& t);
private:
future<> try_perform_cleanup(owned_ranges_ptr sorted_owned_ranges, compaction::table_state& t);
// Add sst to or remove it from the respective compaction_state.sstables_requiring_cleanup set.
bool update_sstable_cleanup_state(table_state& t, const sstables::shared_sstable& sst, const dht::token_range_vector& sorted_owned_ranges);
public:
// Submit a table to be upgraded and wait for its termination.
future<> perform_sstable_upgrade(owned_ranges_ptr sorted_owned_ranges, compaction::table_state& t, bool exclude_current_version);
@@ -409,9 +404,8 @@ public:
return _tombstone_gc_state;
};
// Uncoditionally erase sst from `sstables_requiring_cleanup`
// Returns true iff sst was found and erased.
bool erase_sstable_cleanup_state(table_state& t, const sstables::shared_sstable& sst);
// Add sst to or remove it from the respective compaction_state.sstables_requiring_cleanup set.
bool update_sstable_cleanup_state(table_state& t, const sstables::shared_sstable& sst, owned_ranges_ptr owned_ranges_ptr);
// checks if the sstable is in the respective compaction_state.sstables_requiring_cleanup set.
bool requires_cleanup(table_state& t, const sstables::shared_sstable& sst) const;

View File

@@ -35,7 +35,7 @@ struct compaction_state {
compaction_backlog_tracker backlog_tracker;
std::unordered_set<sstables::shared_sstable> sstables_requiring_cleanup;
compaction::owned_ranges_ptr owned_ranges_ptr;
owned_ranges_ptr owned_ranges_ptr;
explicit compaction_state(table_state& t);
compaction_state(compaction_state&&) = delete;

View File

@@ -37,10 +37,6 @@ compaction_descriptor leveled_compaction_strategy::get_sstables_for_compaction(t
return candidate;
}
if (!table_s.tombstone_gc_enabled()) {
return compaction_descriptor();
}
// if there is no sstable to compact in standard way, try compacting based on droppable tombstone ratio
// unlike stcs, lcs can look for sstable with highest droppable tombstone ratio, so as not to choose
// a sstable which droppable data shadow data in older sstable, by starting from highest levels which

View File

@@ -164,10 +164,6 @@ size_tiered_compaction_strategy::get_sstables_for_compaction(table_state& table_
return sstables::compaction_descriptor(std::move(most_interesting), service::get_local_compaction_priority());
}
if (!table_s.tombstone_gc_enabled()) {
return compaction_descriptor();
}
// if there is no sstable to compact in standard way, try compacting single sstable whose droppable tombstone
// ratio is greater than threshold.
// prefer oldest sstables from biggest size tiers because they will be easier to satisfy conditions for

View File

@@ -9,8 +9,6 @@
#pragma once
#include <seastar/core/condition-variable.hh>
#include "schema/schema_fwd.hh"
#include "compaction_descriptor.hh"
@@ -50,24 +48,9 @@ public:
virtual api::timestamp_type min_memtable_timestamp() const = 0;
virtual future<> on_compaction_completion(sstables::compaction_completion_desc desc, sstables::offstrategy offstrategy) = 0;
virtual bool is_auto_compaction_disabled_by_user() const noexcept = 0;
virtual bool tombstone_gc_enabled() const noexcept = 0;
virtual const tombstone_gc_state& get_tombstone_gc_state() const noexcept = 0;
virtual compaction_backlog_tracker& get_backlog_tracker() = 0;
virtual const std::string& get_group_id() const noexcept = 0;
virtual seastar::condition_variable& get_staging_done_condition() noexcept = 0;
};
} // namespace compaction
}
namespace fmt {
template <>
struct formatter<compaction::table_state> : formatter<std::string_view> {
template <typename FormatContext>
auto format(const compaction::table_state& t, FormatContext& ctx) const {
auto s = t.schema();
return fmt::format_to(ctx.out(), "{}.{} compaction_group={}", s->ks_name(), s->cf_name(), t.get_group_id());
}
};
} // namespace fmt

View File

@@ -128,44 +128,4 @@ future<> shard_upgrade_sstables_compaction_task_impl::run() {
});
}
future<> scrub_sstables_compaction_task_impl::run() {
_stats = co_await _db.map_reduce0([&] (replica::database& db) -> future<sstables::compaction_stats> {
sstables::compaction_stats stats;
tasks::task_info parent_info{_status.id, _status.shard};
auto& compaction_module = db.get_compaction_manager().get_task_manager_module();
auto task = co_await compaction_module.make_and_start_task<shard_scrub_sstables_compaction_task_impl>(parent_info, _status.keyspace, _status.id, db, _column_families, _opts, stats);
co_await task->done();
co_return stats;
}, sstables::compaction_stats{}, std::plus<sstables::compaction_stats>());
}
tasks::is_internal shard_scrub_sstables_compaction_task_impl::is_internal() const noexcept {
return tasks::is_internal::yes;
}
future<> shard_scrub_sstables_compaction_task_impl::run() {
_stats = co_await map_reduce(_column_families, [&] (sstring cfname) -> future<sstables::compaction_stats> {
sstables::compaction_stats stats{};
tasks::task_info parent_info{_status.id, _status.shard};
auto& compaction_module = _db.get_compaction_manager().get_task_manager_module();
auto task = co_await compaction_module.make_and_start_task<table_scrub_sstables_compaction_task_impl>(parent_info, _status.keyspace, cfname, _status.id, _db, _opts, stats);
co_await task->done();
co_return stats;
}, sstables::compaction_stats{}, std::plus<sstables::compaction_stats>());
}
tasks::is_internal table_scrub_sstables_compaction_task_impl::is_internal() const noexcept {
return tasks::is_internal::yes;
}
future<> table_scrub_sstables_compaction_task_impl::run() {
auto& cm = _db.get_compaction_manager();
auto& cf = _db.find_column_family(_status.keyspace, _status.table);
co_await cf.parallel_foreach_table_state([&] (compaction::table_state& ts) mutable -> future<> {
auto r = co_await cm.perform_sstable_scrub(ts, _opts);
_stats += r.value_or(sstables::compaction_stats{});
});
}
}

View File

@@ -8,7 +8,6 @@
#pragma once
#include "compaction/compaction.hh"
#include "replica/database_fwd.hh"
#include "schema/schema_fwd.hh"
#include "tasks/task_manager.hh"
@@ -214,9 +213,9 @@ protected:
virtual future<> run() override;
};
class sstables_compaction_task_impl : public compaction_task_impl {
class rewrite_sstables_compaction_task_impl : public compaction_task_impl {
public:
sstables_compaction_task_impl(tasks::task_manager::module_ptr module,
rewrite_sstables_compaction_task_impl(tasks::task_manager::module_ptr module,
tasks::task_id id,
unsigned sequence_number,
std::string keyspace,
@@ -235,7 +234,7 @@ protected:
virtual future<> run() override = 0;
};
class upgrade_sstables_compaction_task_impl : public sstables_compaction_task_impl {
class upgrade_sstables_compaction_task_impl : public rewrite_sstables_compaction_task_impl {
private:
sharded<replica::database>& _db;
std::vector<table_id> _table_infos;
@@ -246,7 +245,7 @@ public:
sharded<replica::database>& db,
std::vector<table_id> table_infos,
bool exclude_current_version) noexcept
: sstables_compaction_task_impl(module, tasks::task_id::create_random_id(), module->new_sequence_number(), std::move(keyspace), "", "", tasks::task_id::create_null_id())
: rewrite_sstables_compaction_task_impl(module, tasks::task_id::create_random_id(), module->new_sequence_number(), std::move(keyspace), "", "", tasks::task_id::create_null_id())
, _db(db)
, _table_infos(std::move(table_infos))
, _exclude_current_version(exclude_current_version)
@@ -255,7 +254,7 @@ protected:
virtual future<> run() override;
};
class shard_upgrade_sstables_compaction_task_impl : public sstables_compaction_task_impl {
class shard_upgrade_sstables_compaction_task_impl : public rewrite_sstables_compaction_task_impl {
private:
replica::database& _db;
std::vector<table_id> _table_infos;
@@ -267,7 +266,7 @@ public:
replica::database& db,
std::vector<table_id> table_infos,
bool exclude_current_version) noexcept
: sstables_compaction_task_impl(module, tasks::task_id::create_random_id(), module->new_sequence_number(), std::move(keyspace), "", "", parent_id)
: rewrite_sstables_compaction_task_impl(module, tasks::task_id::create_random_id(), module->new_sequence_number(), std::move(keyspace), "", "", parent_id)
, _db(db)
, _table_infos(std::move(table_infos))
, _exclude_current_version(exclude_current_version)
@@ -278,79 +277,6 @@ protected:
virtual future<> run() override;
};
class scrub_sstables_compaction_task_impl : public sstables_compaction_task_impl {
private:
sharded<replica::database>& _db;
std::vector<sstring> _column_families;
sstables::compaction_type_options::scrub _opts;
sstables::compaction_stats& _stats;
public:
scrub_sstables_compaction_task_impl(tasks::task_manager::module_ptr module,
std::string keyspace,
sharded<replica::database>& db,
std::vector<sstring> column_families,
sstables::compaction_type_options::scrub opts,
sstables::compaction_stats& stats) noexcept
: sstables_compaction_task_impl(module, tasks::task_id::create_random_id(), module->new_sequence_number(), std::move(keyspace), "", "", tasks::task_id::create_null_id())
, _db(db)
, _column_families(std::move(column_families))
, _opts(opts)
, _stats(stats)
{}
protected:
virtual future<> run() override;
};
class shard_scrub_sstables_compaction_task_impl : public sstables_compaction_task_impl {
private:
replica::database& _db;
std::vector<sstring> _column_families;
sstables::compaction_type_options::scrub _opts;
sstables::compaction_stats& _stats;
public:
shard_scrub_sstables_compaction_task_impl(tasks::task_manager::module_ptr module,
std::string keyspace,
tasks::task_id parent_id,
replica::database& db,
std::vector<sstring> column_families,
sstables::compaction_type_options::scrub opts,
sstables::compaction_stats& stats) noexcept
: sstables_compaction_task_impl(module, tasks::task_id::create_random_id(), module->new_sequence_number(), std::move(keyspace), "", "", parent_id)
, _db(db)
, _column_families(std::move(column_families))
, _opts(opts)
, _stats(stats)
{}
virtual tasks::is_internal is_internal() const noexcept override;
protected:
virtual future<> run() override;
};
class table_scrub_sstables_compaction_task_impl : public sstables_compaction_task_impl {
private:
replica::database& _db;
sstables::compaction_type_options::scrub _opts;
sstables::compaction_stats& _stats;
public:
table_scrub_sstables_compaction_task_impl(tasks::task_manager::module_ptr module,
std::string keyspace,
std::string table,
tasks::task_id parent_id,
replica::database& db,
sstables::compaction_type_options::scrub opts,
sstables::compaction_stats& stats) noexcept
: sstables_compaction_task_impl(module, tasks::task_id::create_random_id(), module->new_sequence_number(), std::move(keyspace), std::move(table), "", parent_id)
, _db(db)
, _opts(opts)
, _stats(stats)
{}
virtual tasks::is_internal is_internal() const noexcept override;
protected:
virtual future<> run() override;
};
class task_manager_module : public tasks::task_manager::module {
public:
task_manager_module(tasks::task_manager& tm) noexcept : tasks::task_manager::module(tm, "compaction") {}

View File

@@ -284,10 +284,6 @@ time_window_compaction_strategy::get_next_non_expired_sstables(table_state& tabl
return most_interesting;
}
if (!table_s.tombstone_gc_enabled()) {
return {};
}
// if there is no sstable to compact in standard way, try compacting single sstable whose droppable tombstone
// ratio is greater than threshold.
auto e = boost::range::remove_if(non_expiring_sstables, [this, compaction_time, &table_s] (const shared_sstable& sst) -> bool {

View File

@@ -31,10 +31,25 @@ public:
const dht::ring_position_view& position() const {
return *_rpv;
}
std::strong_ordering operator<=>(const compatible_ring_position_or_view& other) const {
return dht::ring_position_tri_compare(*_schema, position(), other.position());
friend std::strong_ordering tri_compare(const compatible_ring_position_or_view& x, const compatible_ring_position_or_view& y) {
return dht::ring_position_tri_compare(*x._schema, x.position(), y.position());
}
bool operator==(const compatible_ring_position_or_view& other) const {
return *this <=> other == 0;
friend bool operator<(const compatible_ring_position_or_view& x, const compatible_ring_position_or_view& y) {
return tri_compare(x, y) < 0;
}
friend bool operator<=(const compatible_ring_position_or_view& x, const compatible_ring_position_or_view& y) {
return tri_compare(x, y) <= 0;
}
friend bool operator>(const compatible_ring_position_or_view& x, const compatible_ring_position_or_view& y) {
return tri_compare(x, y) > 0;
}
friend bool operator>=(const compatible_ring_position_or_view& x, const compatible_ring_position_or_view& y) {
return tri_compare(x, y) >= 0;
}
friend bool operator==(const compatible_ring_position_or_view& x, const compatible_ring_position_or_view& y) {
return tri_compare(x, y) == 0;
}
friend bool operator!=(const compatible_ring_position_or_view& x, const compatible_ring_position_or_view& y) {
return tri_compare(x, y) != 0;
}
};

View File

@@ -123,6 +123,10 @@ public:
bool operator==(const iterator& other) const {
return _offset == other._offset && other._i == _i;
}
bool operator!=(const iterator& other) const {
return !(*this == other);
}
};
// A trichotomic comparator defined on @CompoundType representations which
@@ -425,6 +429,7 @@ public:
const value_type& operator*() const { return _current; }
const value_type* operator->() const { return &_current; }
bool operator!=(const iterator& i) const { return _v.begin() != i._v.begin(); }
bool operator==(const iterator& i) const { return _v.begin() == i._v.begin(); }
friend class composite;
@@ -631,6 +636,7 @@ public:
}
bool operator==(const composite_view& k) const { return k._bytes == _bytes && k._is_compound == _is_compound; }
bool operator!=(const composite_view& k) const { return !(k == *this); }
friend fmt::formatter<composite_view>;
};

View File

@@ -175,6 +175,10 @@ bool compression_parameters::operator==(const compression_parameters& other) con
&& _crc_check_chance == other._crc_check_chance;
}
bool compression_parameters::operator!=(const compression_parameters& other) const {
return !(*this == other);
}
void compression_parameters::validate_options(const std::map<sstring, sstring>& options) {
// currently, there are no options specific to a particular compressor
static std::set<sstring> keywords({

View File

@@ -105,6 +105,7 @@ public:
void validate();
std::map<sstring, sstring> get_options() const;
bool operator==(const compression_parameters& other) const;
bool operator!=(const compression_parameters& other) const;
static compression_parameters no_compression() {
return compression_parameters(nullptr);

View File

@@ -272,7 +272,6 @@ batch_size_fail_threshold_in_kb: 1024
# - alternator-streams
# - alternator-ttl
# - raft
# - tablets
# The directory where hints files are stored if hinted handoff is enabled.
# hints_directory: /var/lib/scylla/hints

View File

@@ -435,8 +435,6 @@ scylla_tests = set([
'test/boost/mutation_writer_test',
'test/boost/mvcc_test',
'test/boost/network_topology_strategy_test',
'test/boost/token_metadata_test',
'test/boost/tablets_test',
'test/boost/nonwrapping_range_test',
'test/boost/observable_test',
'test/boost/partitioner_test',
@@ -509,8 +507,6 @@ scylla_tests = set([
'test/boost/exceptions_fallback_test',
'test/boost/s3_test',
'test/boost/locator_topology_test',
'test/boost/string_format_test',
'test/boost/tagged_integer_test',
'test/manual/ec2_snitch_test',
'test/manual/enormous_table_scan_test',
'test/manual/gce_snitch_test',
@@ -565,20 +561,6 @@ raft_tests = set([
'test/raft/failure_detector_test',
])
wasms = set([
'wasm/return_input.wat',
'wasm/test_complex_null_values.wat',
'wasm/test_fib_called_on_null.wat',
'wasm/test_functions_with_frozen_types.wat',
'wasm/test_mem_grow.wat',
'wasm/test_pow.wat',
'wasm/test_short_ints.wat',
'wasm/test_types_with_and_without_nulls.wat',
'wasm/test_UDA_final.wat',
'wasm/test_UDA_scalar.wat',
'wasm/test_word_double.wat',
])
apps = set([
'scylla',
])
@@ -589,7 +571,7 @@ other = set([
'iotune',
])
all_artifacts = apps | tests | other | wasms
all_artifacts = apps | tests | other
arg_parser = argparse.ArgumentParser('Configure scylla')
arg_parser.add_argument('--out', dest='buildfile', action='store', default='build.ninja',
@@ -681,7 +663,6 @@ scylla_raft_core = [
scylla_core = (['message/messaging_service.cc',
'replica/database.cc',
'replica/table.cc',
'replica/tablets.cc',
'replica/distributed_loader.cc',
'replica/memtable.cc',
'replica/exceptions.cc',
@@ -691,7 +672,6 @@ scylla_core = (['message/messaging_service.cc',
'mutation/frozen_mutation.cc',
'mutation/mutation.cc',
'mutation/mutation_fragment.cc',
'mutation/mutation_fragment_stream_validator.cc',
'mutation/mutation_partition.cc',
'mutation/mutation_partition_v2.cc',
'mutation/mutation_partition_view.cc',
@@ -737,7 +717,6 @@ scylla_core = (['message/messaging_service.cc',
'sstables/sstables.cc',
'sstables/sstables_manager.cc',
'sstables/sstable_set.cc',
'sstables/storage.cc',
'sstables/mx/partition_reversing_data_source.cc',
'sstables/mx/reader.cc',
'sstables/mx/writer.cc',
@@ -863,7 +842,6 @@ scylla_core = (['message/messaging_service.cc',
'validation.cc',
'service/priority_manager.cc',
'service/migration_manager.cc',
'service/tablet_allocator.cc',
'service/storage_proxy.cc',
'query_ranges_to_vnodes.cc',
'service/forward_service.cc',
@@ -953,7 +931,6 @@ scylla_core = (['message/messaging_service.cc',
'query.cc',
'query-result-set.cc',
'locator/abstract_replication_strategy.cc',
'locator/tablets.cc',
'locator/azure_snitch.cc',
'locator/simple_strategy.cc',
'locator/local_strategy.cc',
@@ -1192,7 +1169,7 @@ scylla_tests_generic_dependencies = [
'test/lib/sstable_run_based_compaction_strategy_for_tests.cc',
]
scylla_tests_dependencies = scylla_core + alternator + idls + scylla_tests_generic_dependencies + [
scylla_tests_dependencies = scylla_core + idls + scylla_tests_generic_dependencies + [
'test/lib/cql_assertions.cc',
'test/lib/result_set_assertions.cc',
'test/lib/mutation_source_test.cc',
@@ -1210,7 +1187,6 @@ scylla_perfs = ['test/perf/perf_fast_forward.cc',
'test/perf/perf_row_cache_update.cc',
'test/perf/perf_simple_query.cc',
'test/perf/perf_sstable.cc',
'test/perf/perf_tablets.cc',
'test/perf/perf.cc',
'test/lib/alternator_test_env.cc',
'test/lib/cql_test_env.cc',
@@ -1259,7 +1235,6 @@ pure_boost_tests = set([
'test/boost/vint_serialization_test',
'test/boost/bptree_test',
'test/boost/utf8_test',
'test/boost/string_format_test',
'test/manual/streaming_histogram_test',
])
@@ -1297,7 +1272,7 @@ for t in sorted(scylla_tests):
if t not in tests_not_using_seastar_test_framework:
deps[t] += scylla_tests_dependencies
else:
deps[t] += scylla_core + alternator + idls + scylla_tests_generic_dependencies
deps[t] += scylla_core + idls + scylla_tests_generic_dependencies
perf_tests_seastar_deps = [
'seastar/tests/perf/perf_tests.cc'
@@ -1363,27 +1338,15 @@ deps['test/raft/discovery_test'] = ['test/raft/discovery_test.cc',
'test/lib/log.cc',
'service/raft/discovery.cc'] + scylla_raft_dependencies
wasm_deps = {}
wasm_deps['wasm/return_input.wat'] = 'test/resource/wasm/rust/return_input.rs'
wasm_deps['wasm/test_short_ints.wat'] = 'test/resource/wasm/rust/test_short_ints.rs'
wasm_deps['wasm/test_complex_null_values.wat'] = 'test/resource/wasm/rust/test_complex_null_values.rs'
wasm_deps['wasm/test_functions_with_frozen_types.wat'] = 'test/resource/wasm/rust/test_functions_with_frozen_types.rs'
wasm_deps['wasm/test_types_with_and_without_nulls.wat'] = 'test/resource/wasm/rust/test_types_with_and_without_nulls.rs'
wasm_deps['wasm/test_fib_called_on_null.wat'] = 'test/resource/wasm/c/test_fib_called_on_null.c'
wasm_deps['wasm/test_mem_grow.wat'] = 'test/resource/wasm/c/test_mem_grow.c'
wasm_deps['wasm/test_pow.wat'] = 'test/resource/wasm/c/test_pow.c'
wasm_deps['wasm/test_UDA_final.wat'] = 'test/resource/wasm/c/test_UDA_final.c'
wasm_deps['wasm/test_UDA_scalar.wat'] = 'test/resource/wasm/c/test_UDA_scalar.c'
wasm_deps['wasm/test_word_double.wat'] = 'test/resource/wasm/c/test_word_double.c'
warnings = [
'-Wall',
'-Werror',
'-Wno-mismatched-tags', # clang-only
'-Wno-tautological-compare',
'-Wno-parentheses-equality',
'-Wno-c++11-narrowing',
'-Wno-missing-braces',
'-Wno-ignored-attributes',
'-Wno-overloaded-virtual',
'-Wno-unused-command-line-argument',
@@ -1539,10 +1502,10 @@ default_modes = args.selected_modes or [mode for mode, mode_cfg in modes.items()
build_modes = {m: modes[m] for m in selected_modes}
if args.artifacts:
build_artifacts = set()
build_artifacts = []
for artifact in args.artifacts:
if artifact in all_artifacts:
build_artifacts.add(artifact)
build_artifacts.append(artifact)
else:
print("Ignoring unknown build artifact: {}".format(artifact))
if not build_artifacts:
@@ -1824,32 +1787,7 @@ with open(buildfile, 'w') as f:
description = RUST_SOURCE $out
rule cxxbridge_header
command = cxxbridge --header > $out
rule c2wasm
command = clang --target=wasm32 --no-standard-libraries -Wl,--export-all -Wl,--no-entry $in -o $out
description = C2WASM $out
rule rust2wasm
# The default stack size in Rust is 1MB, which causes oversized allocation warnings,
# because it's allocated in a single chunk as a part of a Wasm Linear Memory.
# We change the stack size to 128KB using the RUSTFLAGS environment variable
# in the command below.
command = RUSTFLAGS="-C link-args=-zstack-size=131072" cargo build --target=wasm32-wasi --example=$example --locked --manifest-path=test/resource/wasm/rust/Cargo.toml --target-dir=$builddir/wasm/ $
&& wasm-opt -Oz $builddir/wasm/wasm32-wasi/debug/examples/$example.wasm -o $builddir/wasm/$example.wasm $
&& wasm-strip $builddir/wasm/$example.wasm
description = RUST2WASM $out
rule wasm2wat
command = wasm2wat $in > $out
description = WASM2WAT $out
''').format(**globals()))
for binary in sorted(wasms):
src = wasm_deps[binary]
wasm = binary[:-4] + '.wasm'
if src.endswith('.rs'):
f.write(f'build $builddir/{wasm}: rust2wasm {src} | test/resource/wasm/rust/Cargo.lock\n')
example_name = binary[binary.rindex('/')+1:-4]
f.write(f' example = {example_name}\n')
else:
f.write(f'build $builddir/{wasm}: c2wasm {src}\n')
f.write(f'build $builddir/{binary}: wasm2wat $builddir/{wasm}\n')
for mode in build_modes:
modeval = modes[mode]
fmt_lib = 'fmt'
@@ -1914,10 +1852,9 @@ with open(buildfile, 'w') as f:
description = RUST_LIB $out
''').format(mode=mode, antlr3_exec=antlr3_exec, fmt_lib=fmt_lib, test_repeat=test_repeat, test_timeout=test_timeout, **modeval))
f.write(
'build {mode}-build: phony {artifacts} {wasms}\n'.format(
'build {mode}-build: phony {artifacts}\n'.format(
mode=mode,
artifacts=str.join(' ', ['$builddir/' + mode + '/' + x for x in sorted(build_artifacts - wasms)]),
wasms = str.join(' ', ['$builddir/' + x for x in sorted(build_artifacts & wasms)]),
artifacts=str.join(' ', ['$builddir/' + mode + '/' + x for x in sorted(build_artifacts)])
)
)
include_cxx_target = f'{mode}-build' if not args.dist_only else ''
@@ -1934,7 +1871,7 @@ with open(buildfile, 'w') as f:
seastar_dep = f'$builddir/{mode}/seastar/libseastar.{seastar_lib_ext}'
seastar_testing_dep = f'$builddir/{mode}/seastar/libseastar_testing.{seastar_lib_ext}'
for binary in sorted(build_artifacts):
if binary in other or binary in wasms:
if binary in other:
continue
srcs = deps[binary]
objs = ['$builddir/' + mode + '/' + src.replace('.cc', '.o')
@@ -1967,7 +1904,7 @@ with open(buildfile, 'w') as f:
if binary not in tests_not_using_seastar_test_framework:
local_libs += ' ' + "$seastar_testing_libs_{}".format(mode)
else:
local_libs += ' ' + '-lgnutls' + ' ' + '-lboost_unit_test_framework'
local_libs += ' ' + '-lgnutls'
# Our code's debugging information is huge, and multiplied
# by many tests yields ridiculous amounts of disk space.
# So we strip the tests by default; The user can very
@@ -2022,10 +1959,9 @@ with open(buildfile, 'w') as f:
)
f.write(
'build {mode}-test: test.{mode} {test_executables} $builddir/{mode}/scylla {wasms}\n'.format(
'build {mode}-test: test.{mode} {test_executables} $builddir/{mode}/scylla\n'.format(
mode=mode,
test_executables=' '.join(['$builddir/{}/{}'.format(mode, binary) for binary in sorted(tests)]),
wasms=' '.join([f'$builddir/{binary}' for binary in sorted(wasms)]),
)
)
f.write(
@@ -2089,14 +2025,13 @@ with open(buildfile, 'w') as f:
for cc in grammar.sources('$builddir/{}/gen'.format(mode)):
obj = cc.replace('.cpp', '.o')
f.write('build {}: cxx.{} {} || {}\n'.format(obj, mode, cc, ' '.join(serializers)))
flags = '-Wno-parentheses-equality'
if cc.endswith('Parser.cpp'):
# Unoptimized parsers end up using huge amounts of stack space and overflowing their stack
flags += ' -O1' if modes[mode]['optimization-level'] in ['0', 'g', 's'] else ''
flags = '-O1' if modes[mode]['optimization-level'] in ['0', 'g', 's'] else ''
if has_sanitize_address_use_after_scope:
flags += ' -fno-sanitize-address-use-after-scope'
f.write(f' obj_cxxflags = {flags}\n')
f.write(' obj_cxxflags = %s\n' % flags)
f.write(f'build $builddir/{mode}/gen/empty.cc: gen\n')
for hh in headers:
f.write('build $builddir/{mode}/{hh}.o: checkhh.{mode} {hh} | $builddir/{mode}/gen/empty.cc || {gen_headers_dep}\n'.format(
@@ -2169,9 +2104,6 @@ with open(buildfile, 'w') as f:
f.write(
'build check: phony {}\n'.format(' '.join(['{mode}-check'.format(mode=mode) for mode in default_modes]))
)
f.write(
'build wasm: phony {}\n'.format(' '.join([f'$builddir/{binary}' for binary in sorted(wasms)]))
)
f.write(textwrap.dedent(f'''\
build dist-unified-tar: phony {' '.join([f'$builddir/{mode}/dist/tar/{scylla_product}-unified-{scylla_version}-{scylla_release}.{arch}.tar.gz' for mode in default_modes])}

View File

@@ -78,6 +78,9 @@ public:
return id() == other.id() && value() == other.value()
&& logical_clock() == other.logical_clock();
}
bool operator!=(const basic_counter_shard_view& other) const {
return !(*this == other);
}
struct less_compare_by_id {
bool operator()(const basic_counter_shard_view& x, const basic_counter_shard_view& y) const {

View File

@@ -7,7 +7,7 @@ generate_cql_grammar(
SOURCES cql_grammar_srcs)
set_source_files_properties(${cql_grammar_srcs}
PROPERTIES
COMPILE_FLAGS "-Wno-uninitialized -Wno-parentheses-equality")
COMPILE_FLAGS "-Wno-uninitialized")
add_library(cql3 STATIC)
target_sources(cql3

View File

@@ -1773,12 +1773,7 @@ relation returns [expression e]
: name=cident type=relationType t=term { $e = binary_operator(unresolved_identifier{std::move(name)}, type, std::move(t)); }
| K_TOKEN l=tupleOfIdentifiers type=relationType t=term
{
$e = binary_operator(
function_call{functions::function_name::native_function("token"), std::move(l.elements)},
type,
std::move(t));
}
{ $e = binary_operator(token{std::move(l.elements)}, type, std::move(t)); }
| name=cident K_IS K_NOT K_NULL {
$e = binary_operator(unresolved_identifier{std::move(name)}, oper_t::IS_NOT, make_untyped_null()); }
| name=cident K_IN marker1=marker

View File

@@ -57,7 +57,13 @@ public:
const cache_key_type& key() const { return _key; }
bool operator==(const authorized_prepared_statements_cache_key&) const = default;
bool operator==(const authorized_prepared_statements_cache_key& other) const {
return _key == other._key;
}
bool operator!=(const authorized_prepared_statements_cache_key& other) const {
return !(*this == other);
}
static size_t hash(const auth::authenticated_user& user, const cql3::prepared_cache_key_type::cache_key_type& prep_cache_key) {
return utils::hash_combine(std::hash<auth::authenticated_user>()(user), utils::tuple_hash()(prep_cache_key));

View File

@@ -11,6 +11,8 @@
#include "cql3/util.hh"
#include "cql3/query_options.hh"
#include <regex>
namespace cql3 {
column_identifier::column_identifier(sstring raw_text, bool keep_case) {
@@ -94,6 +96,10 @@ bool column_identifier_raw::operator==(const column_identifier_raw& other) const
return _text == other._text;
}
bool column_identifier_raw::operator!=(const column_identifier_raw& other) const {
return !operator==(other);
}
sstring column_identifier_raw::to_string() const {
return _text;
}

View File

@@ -88,6 +88,8 @@ public:
bool operator==(const column_identifier_raw& other) const;
bool operator!=(const column_identifier_raw& other) const;
virtual sstring to_string() const;
sstring to_cql_string() const;

View File

@@ -205,10 +205,10 @@ class cql3_type::raw_ut : public raw {
virtual sstring to_string() const override {
if (is_frozen()) {
return format("frozen<{}>", _name.to_cql_string());
return format("frozen<{}>", _name.to_string());
}
return _name.to_cql_string();
return _name.to_string();
}
public:
raw_ut(ut_name name)

View File

@@ -86,43 +86,57 @@ private:
{
using namespace antlr3;
std::stringstream msg;
// Antlr3 has a function ex->displayRecognitionError() which is
// supposed to nicely print the recognition exception. Unfortunately
// it is buggy - see https://github.com/antlr/antlr3/issues/191
// and not being fixed, so let's copy it here and fix it here.
switch (ex->getType()) {
case ExceptionType::RECOGNITION_EXCEPTION:
case ExceptionType::EARLY_EXIT_EXCEPTION:
default:
// Unknown syntax error - the parser can't figure out what
// specific token is missing or unwanted.
msg << ": Syntax error";
break;
case ExceptionType::MISSING_TOKEN_EXCEPTION:
msg << ": Missing ";
if (recognizer.is_eof_token(ex->get_expecting())) {
msg << "EOF";
} else if (token_names) {
msg << reinterpret_cast<const char*>(token_names[ex->get_expecting()]);
} else {
msg << ex->get_expecting();
case ExceptionType::UNWANTED_TOKEN_EXCEPTION: {
msg << "extraneous input " << get_token_error_display(recognizer, ex->get_token());
if (token_names != nullptr) {
std::string token_name;
if (recognizer.is_eof_token(ex->get_expecting())) {
token_name = "EOF";
} else {
token_name = reinterpret_cast<const char*>(token_names[ex->get_expecting()]);
}
msg << " expecting " << token_name;
}
break;
case ExceptionType::UNWANTED_TOKEN_EXCEPTION:
case ExceptionType::MISMATCHED_SET_EXCEPTION:
msg << ": Unexpected '";
msg << recognizer.token_text(ex->get_token());
msg << "'";
break;
case ExceptionType::NO_VIABLE_ALT_EXCEPTION:
msg << "no viable alternative at input '";
msg << recognizer.token_text(ex->get_token());
msg << "'";
}
case ExceptionType::MISSING_TOKEN_EXCEPTION: {
std::string token_name;
if (token_names == nullptr) {
token_name = "(" + std::to_string(ex->get_expecting()) + ")";
} else {
if (recognizer.is_eof_token(ex->get_expecting())) {
token_name = "EOF";
} else {
token_name = reinterpret_cast<const char*>(token_names[ex->get_expecting()]);
}
}
msg << "missing " << token_name << " at " << get_token_error_display(recognizer, ex->get_token());
break;
}
case ExceptionType::NO_VIABLE_ALT_EXCEPTION: {
msg << "no viable alternative at input " << get_token_error_display(recognizer, ex->get_token());
break;
}
default:
// AntLR Exception class has a bug of dereferencing a null
// pointer in the displayRecognitionError. The following
// if statement makes sure it will not be null before the
// call to that function (displayRecognitionError).
// bug reference: https://github.com/antlr/antlr3/issues/191
if (!ex->get_expectingSet()) {
ex->set_expectingSet(&_empty_bit_list);
}
ex->displayRecognitionError(token_names, msg);
}
return msg.str();
}
std::string get_token_error_display(RecognizerType& recognizer, const TokenType* token)
{
return "'" + recognizer.token_text(token) + "'";
}
#if 0
/**

View File

@@ -56,6 +56,10 @@ bool operator==(const expression& e1, const expression& e2) {
}, e1);
}
bool operator!=(const expression& e1, const expression& e2) {
return !(e1 == e2);
}
expression::expression(const expression& o)
: _v(std::make_unique<impl>(*o._v)) {
}
@@ -66,6 +70,24 @@ expression::operator=(const expression& o) {
return *this;
}
token::token(std::vector<expression> args_in)
: args(std::move(args_in)) {
}
token::token(const std::vector<const column_definition*>& col_defs) {
args.reserve(col_defs.size());
for (const column_definition* col_def : col_defs) {
args.push_back(column_value(col_def));
}
}
token::token(const std::vector<::shared_ptr<column_identifier_raw>>& cols) {
args.reserve(cols.size());
for(const ::shared_ptr<column_identifier_raw>& col : cols) {
args.push_back(unresolved_identifier{col});
}
}
binary_operator::binary_operator(expression lhs, oper_t op, expression rhs, comparison_order order)
: lhs(std::move(lhs))
, op(op)
@@ -542,11 +564,89 @@ value_set intersection(value_set a, value_set b, const abstract_type* type) {
return std::visit(intersection_visitor{type}, std::move(a), std::move(b));
}
bool is_satisfied_by(const binary_operator& opr, const evaluation_inputs& inputs) {
if (is<token>(opr.lhs)) {
// The RHS value was already used to ensure we fetch only rows in the specified
// token range. It is impossible for any fetched row not to match now.
// When token restrictions are present we forbid all other restrictions on partition key.
// This means that the partition range is defined solely by restrictions on token.
// When is_satisifed_by is used by filtering we can be sure that the token restrictions
// are fulfilled. In the future it will be possible to evaluate() a token,
// and we will be able to get rid of this risky if.
return true;
}
raw_value binop_eval_result = evaluate(opr, inputs);
if (binop_eval_result.is_null()) {
return false;
}
if (binop_eval_result.is_empty_value()) {
on_internal_error(expr_logger, format("is_satisfied_by: binary operator evaluated to EMPTY_VALUE: {}", opr));
}
return binop_eval_result.view().deserialize<bool>(*boolean_type);
}
} // anonymous namespace
bool is_satisfied_by(const expression& restr, const evaluation_inputs& inputs) {
static auto true_value = managed_bytes_opt(data_value(true).serialize_nonnull());
return evaluate(restr, inputs).to_managed_bytes_opt() == true_value;
return expr::visit(overloaded_functor{
[] (const constant& constant_val) {
std::optional<bool> bool_val = get_bool_value(constant_val);
if (bool_val.has_value()) {
return *bool_val;
}
on_internal_error(expr_logger,
"is_satisfied_by: a constant that is not a bool value cannot serve as a restriction by itself");
},
[&] (const conjunction& conj) {
return boost::algorithm::all_of(conj.children, [&] (const expression& c) {
return is_satisfied_by(c, inputs);
});
},
[&] (const binary_operator& opr) { return is_satisfied_by(opr, inputs); },
[] (const column_value&) -> bool {
on_internal_error(expr_logger, "is_satisfied_by: a column cannot serve as a restriction by itself");
},
[] (const subscript&) -> bool {
on_internal_error(expr_logger, "is_satisfied_by: a subscript cannot serve as a restriction by itself");
},
[] (const token&) -> bool {
on_internal_error(expr_logger, "is_satisfied_by: the token function cannot serve as a restriction by itself");
},
[] (const unresolved_identifier&) -> bool {
on_internal_error(expr_logger, "is_satisfied_by: an unresolved identifier cannot serve as a restriction");
},
[] (const column_mutation_attribute&) -> bool {
on_internal_error(expr_logger, "is_satisfied_by: the writetime/ttl cannot serve as a restriction by itself");
},
[] (const function_call&) -> bool {
on_internal_error(expr_logger, "is_satisfied_by: a function call cannot serve as a restriction by itself");
},
[] (const cast&) -> bool {
on_internal_error(expr_logger, "is_satisfied_by: a a type cast cannot serve as a restriction by itself");
},
[] (const field_selection&) -> bool {
on_internal_error(expr_logger, "is_satisfied_by: a field selection cannot serve as a restriction by itself");
},
[] (const bind_variable&) -> bool {
on_internal_error(expr_logger, "is_satisfied_by: a bind variable cannot serve as a restriction by itself");
},
[] (const untyped_constant&) -> bool {
on_internal_error(expr_logger, "is_satisfied_by: an untyped constant cannot serve as a restriction by itself");
},
[] (const tuple_constructor&) -> bool {
on_internal_error(expr_logger, "is_satisfied_by: a tuple constructor cannot serve as a restriction by itself");
},
[] (const collection_constructor&) -> bool {
on_internal_error(expr_logger, "is_satisfied_by: a collection constructor cannot serve as a restriction by itself");
},
[] (const usertype_constructor&) -> bool {
on_internal_error(expr_logger, "is_satisfied_by: a user type constructor cannot serve as a restriction by itself");
},
}, restr);
}
namespace {
@@ -667,15 +767,7 @@ nonwrapping_range<clustering_key_prefix> to_range(oper_t op, const clustering_ke
return to_range<const clustering_key_prefix&>(op, val);
}
// When cdef == nullptr it finds possible token values instead of column values.
// When finding token values the table_schema_opt argument has to point to a valid schema,
// but it isn't used when finding values for column.
// The schema is needed to find out whether a call to token() function represents
// the partition token.
static value_set possible_lhs_values(const column_definition* cdef,
const expression& expr,
const query_options& options,
const schema* table_schema_opt) {
value_set possible_lhs_values(const column_definition* cdef, const expression& expr, const query_options& options) {
const auto type = cdef ? &cdef->type->without_reversed() : long_type.get();
return expr::visit(overloaded_functor{
[] (const constant& constant_val) {
@@ -691,7 +783,7 @@ static value_set possible_lhs_values(const column_definition* cdef,
return boost::accumulate(conj.children, unbounded_value_set,
[&] (const value_set& acc, const expression& child) {
return intersection(
std::move(acc), possible_lhs_values(cdef, child, options, table_schema_opt), type);
std::move(acc), possible_lhs_values(cdef, child, options), type);
});
},
[&] (const binary_operator& oper) -> value_set {
@@ -771,11 +863,7 @@ static value_set possible_lhs_values(const column_definition* cdef,
}
return unbounded_value_set;
},
[&] (const function_call& token_fun_call) -> value_set {
if (!is_partition_token_for_schema(token_fun_call, *table_schema_opt)) {
on_internal_error(expr_logger, "possible_lhs_values: function calls are not supported as the LHS of a binary expression");
}
[&] (token) -> value_set {
if (cdef) {
return unbounded_value_set;
}
@@ -817,6 +905,9 @@ static value_set possible_lhs_values(const column_definition* cdef,
[] (const column_mutation_attribute&) -> value_set {
on_internal_error(expr_logger, "possible_lhs_values: writetime/ttl are not supported as the LHS of a binary expression");
},
[] (const function_call&) -> value_set {
on_internal_error(expr_logger, "possible_lhs_values: function calls are not supported as the LHS of a binary expression");
},
[] (const cast&) -> value_set {
on_internal_error(expr_logger, "possible_lhs_values: typecasts are not supported as the LHS of a binary expression");
},
@@ -843,8 +934,11 @@ static value_set possible_lhs_values(const column_definition* cdef,
[] (const subscript&) -> value_set {
on_internal_error(expr_logger, "possible_lhs_values: a subscript cannot serve as a restriction by itself");
},
[] (const token&) -> value_set {
on_internal_error(expr_logger, "possible_lhs_values: the token function cannot serve as a restriction by itself");
},
[] (const unresolved_identifier&) -> value_set {
on_internal_error(expr_logger, "possible_lhs_values: an unresolved identifier cannot serve as a restriction");
on_internal_error(expr_logger, "is_satisfied_by: an unresolved identifier cannot serve as a restriction");
},
[] (const column_mutation_attribute&) -> value_set {
on_internal_error(expr_logger, "possible_lhs_values: the writetime/ttl functions cannot serve as a restriction by itself");
@@ -876,14 +970,6 @@ static value_set possible_lhs_values(const column_definition* cdef,
}, expr);
}
value_set possible_column_values(const column_definition* col, const expression& e, const query_options& options) {
return possible_lhs_values(col, e, options, nullptr);
}
value_set possible_partition_token_values(const expression& e, const query_options& options, const schema& table_schema) {
return possible_lhs_values(nullptr, e, options, &table_schema);
}
nonwrapping_range<managed_bytes> to_range(const value_set& s) {
return std::visit(overloaded_functor{
[] (const nonwrapping_range<managed_bytes>& r) { return r; },
@@ -931,7 +1017,7 @@ secondary_index::index::supports_expression_v is_supported_by_helper(const expre
// We don't use index table for multi-column restrictions, as it cannot avoid filtering.
return index::supports_expression_v::from_bool(false);
},
[&] (const function_call&) { return index::supports_expression_v::from_bool(false); },
[&] (const token&) { return index::supports_expression_v::from_bool(false); },
[&] (const subscript& s) -> ret_t {
const column_value& col = get_subscripted_column(s);
return idx.supports_subscript_expression(*col.col, oper.op);
@@ -951,6 +1037,9 @@ secondary_index::index::supports_expression_v is_supported_by_helper(const expre
[&] (const column_mutation_attribute&) -> ret_t {
on_internal_error(expr_logger, "is_supported_by: writetime/ttl are not supported as the LHS of a binary expression");
},
[&] (const function_call&) -> ret_t {
on_internal_error(expr_logger, "is_supported_by: function calls are not supported as the LHS of a binary expression");
},
[&] (const cast&) -> ret_t {
on_internal_error(expr_logger, "is_supported_by: typecasts are not supported as the LHS of a binary expression");
},
@@ -1017,7 +1106,7 @@ std::ostream& operator<<(std::ostream& os, const column_value& cv) {
std::ostream& operator<<(std::ostream& os, const expression& expr) {
expression::printer pr {
.expr_to_print = expr,
.debug_mode = false
.debug_mode = true
};
return os << pr;
@@ -1074,6 +1163,9 @@ std::ostream& operator<<(std::ostream& os, const expression::printer& pr) {
}
}
},
[&] (const token& t) {
fmt::print(os, "token({})", fmt::join(t.args | transformed(to_printer), ", "));
},
[&] (const column_value& col) {
fmt::print(os, "{}", cql3::util::maybe_quote(col.col->name_as_text()));
},
@@ -1093,18 +1185,14 @@ std::ostream& operator<<(std::ostream& os, const expression::printer& pr) {
to_printer(cma.column));
},
[&] (const function_call& fc) {
if (is_token_function(fc)) {
fmt::print(os, "token({})", fmt::join(fc.args | transformed(to_printer), ", "));
} else {
std::visit(overloaded_functor{
[&] (const functions::function_name& named) {
fmt::print(os, "{}({})", named, fmt::join(fc.args | transformed(to_printer), ", "));
},
[&] (const shared_ptr<functions::function>& anon) {
fmt::print(os, "<anonymous function>({})", fmt::join(fc.args | transformed(to_printer), ", "));
},
}, fc.func);
}
std::visit(overloaded_functor{
[&] (const functions::function_name& named) {
fmt::print(os, "{}({})", named, fmt::join(fc.args | transformed(to_printer), ", "));
},
[&] (const shared_ptr<functions::function>& anon) {
fmt::print(os, "<anonymous function>({})", fmt::join(fc.args | transformed(to_printer), ", "));
},
}, fc.func);
},
[&] (const cast& c) {
std::visit(overloaded_functor{
@@ -1279,9 +1367,9 @@ expression replace_column_def(const expression& expr, const column_definition* n
});
}
expression replace_partition_token(const expression& expr, const column_definition* new_cdef, const schema& table_schema) {
expression replace_token(const expression& expr, const column_definition* new_cdef) {
return search_and_replace(expr, [&] (const expression& expr) -> std::optional<expression> {
if (is_partition_token_for_schema(expr, table_schema)) {
if (expr::is<token>(expr)) {
return column_value{new_cdef};
} else {
return std::nullopt;
@@ -1355,6 +1443,14 @@ bool recurse_until(const expression& e, const noncopyable_function<bool (const e
}
return false;
},
[&] (const token& tok) {
for (auto& a : tok.args) {
if (auto found = recurse_until(a, predicate_fun)) {
return found;
}
}
return false;
},
[](LeafExpression auto const&) {
return false;
}
@@ -1430,6 +1526,13 @@ expression search_and_replace(const expression& e,
.type = s.type,
};
},
[&](const token& tok) -> expression {
return token {
boost::copy_range<std::vector<expression>>(
tok.args | boost::adaptors::transformed(recurse)
)
};
},
[&] (LeafExpression auto const& e) -> expression {
return e;
},
@@ -1504,6 +1607,7 @@ std::vector<expression> extract_single_column_restrictions_for_column(const expr
}
}
void operator()(const token&) {}
void operator()(const unresolved_identifier&) {}
void operator()(const column_mutation_attribute&) {}
void operator()(const function_call&) {}
@@ -1667,6 +1771,9 @@ cql3::raw_value evaluate(const expression& e, const evaluation_inputs& inputs) {
[&](const conjunction& conj) -> cql3::raw_value {
return evaluate(conj, inputs);
},
[](const token&) -> cql3::raw_value {
on_internal_error(expr_logger, "Can't evaluate token");
},
[](const unresolved_identifier&) -> cql3::raw_value {
on_internal_error(expr_logger, "Can't evaluate unresolved_identifier");
},
@@ -2206,6 +2313,11 @@ void fill_prepare_context(expression& e, prepare_context& ctx) {
fill_prepare_context(child, ctx);
}
},
[&](token& tok) {
for (expression& arg : tok.args) {
fill_prepare_context(arg, ctx);
}
},
[](unresolved_identifier&) {},
[&](column_mutation_attribute& a) {
fill_prepare_context(a.column, ctx);
@@ -2255,6 +2367,9 @@ type_of(const expression& e) {
[] (const column_value& e) {
return e.col->type;
},
[] (const token& e) {
return long_type;
},
[] (const unresolved_identifier& e) -> data_type {
on_internal_error(expr_logger, "evaluating type of unresolved_identifier");
},
@@ -2435,7 +2550,7 @@ sstring get_columns_in_commons(const expression& a, const expression& b) {
}
bytes_opt value_for(const column_definition& cdef, const expression& e, const query_options& options) {
value_set possible_vals = possible_column_values(&cdef, e, options);
value_set possible_vals = possible_lhs_values(&cdef, e, options);
return std::visit(overloaded_functor {
[&](const value_list& val_list) -> bytes_opt {
if (val_list.empty()) {
@@ -2579,69 +2694,5 @@ adjust_for_collection_as_maps(const expression& e) {
});
}
bool is_token_function(const function_call& fun_call) {
static thread_local const functions::function_name token_function_name =
functions::function_name::native_function("token");
// Check that function name is "token"
const functions::function_name& fun_name =
std::visit(overloaded_functor{[](const functions::function_name& fname) { return fname; },
[](const shared_ptr<functions::function>& fun) { return fun->name(); }},
fun_call.func);
return fun_name.has_keyspace() ? fun_name == token_function_name : fun_name.name == token_function_name.name;
}
bool is_token_function(const expression& e) {
const function_call* fun_call = as_if<function_call>(&e);
if (fun_call == nullptr) {
return false;
}
return is_token_function(*fun_call);
}
bool is_partition_token_for_schema(const function_call& fun_call, const schema& table_schema) {
if (!is_token_function(fun_call)) {
return false;
}
if (fun_call.args.size() != table_schema.partition_key_size()) {
return false;
}
auto arguments_iter = fun_call.args.begin();
for (const column_definition& partition_key_col : table_schema.partition_key_columns()) {
const expression& cur_argument = *arguments_iter;
const column_value* cur_col = as_if<column_value>(&cur_argument);
if (cur_col == nullptr) {
// A sanity check that we didn't call the function on an unprepared expression.
if (is<unresolved_identifier>(cur_argument)) {
on_internal_error(expr_logger,
format("called is_partition_token with unprepared expression: {}", fun_call));
}
return false;
}
if (cur_col->col != &partition_key_col) {
return false;
}
arguments_iter++;
}
return true;
}
bool is_partition_token_for_schema(const expression& maybe_token, const schema& table_schema) {
const function_call* fun_call = as_if<function_call>(&maybe_token);
if (fun_call == nullptr) {
return false;
}
return is_partition_token_for_schema(*fun_call, table_schema);
}
} // namespace expr
} // namespace cql3

View File

@@ -70,6 +70,7 @@ struct binary_operator;
struct conjunction;
struct column_value;
struct subscript;
struct token;
struct unresolved_identifier;
struct column_mutation_attribute;
struct function_call;
@@ -88,6 +89,7 @@ concept ExpressionElement
|| std::same_as<T, binary_operator>
|| std::same_as<T, column_value>
|| std::same_as<T, subscript>
|| std::same_as<T, token>
|| std::same_as<T, unresolved_identifier>
|| std::same_as<T, column_mutation_attribute>
|| std::same_as<T, function_call>
@@ -107,6 +109,7 @@ concept invocable_on_expression
&& std::invocable<Func, binary_operator>
&& std::invocable<Func, column_value>
&& std::invocable<Func, subscript>
&& std::invocable<Func, token>
&& std::invocable<Func, unresolved_identifier>
&& std::invocable<Func, column_mutation_attribute>
&& std::invocable<Func, function_call>
@@ -126,6 +129,7 @@ concept invocable_on_expression_ref
&& std::invocable<Func, binary_operator&>
&& std::invocable<Func, column_value&>
&& std::invocable<Func, subscript&>
&& std::invocable<Func, token&>
&& std::invocable<Func, unresolved_identifier&>
&& std::invocable<Func, column_mutation_attribute&>
&& std::invocable<Func, function_call&>
@@ -225,6 +229,18 @@ const column_value& get_subscripted_column(const subscript&);
/// Only columns can be subscripted in CQL, so we can expect that the subscripted expression is a column_value.
const column_value& get_subscripted_column(const expression&);
/// Represents token(c1, c2) function on LHS of an operator relation.
/// args contains arguments to the token function.
struct token {
std::vector<expression> args;
explicit token(std::vector<expression>);
explicit token(const std::vector<const column_definition*>&);
explicit token(const std::vector<::shared_ptr<column_identifier_raw>>&);
friend bool operator==(const token&, const token&) = default;
};
enum class oper_t { EQ, NEQ, LT, LTE, GTE, GT, IN, CONTAINS, CONTAINS_KEY, IS_NOT, LIKE };
/// Describes the nature of clustering-key comparisons. Useful for implementing SCYLLA_CLUSTERING_BOUND.
@@ -413,7 +429,7 @@ struct usertype_constructor {
// now that all expression types are fully defined, we can define expression::impl
struct expression::impl final {
using variant_type = std::variant<
conjunction, binary_operator, column_value, unresolved_identifier,
conjunction, binary_operator, column_value, token, unresolved_identifier,
column_mutation_attribute, function_call, cast, field_selection,
bind_variable, untyped_constant, constant, tuple_constructor, collection_constructor,
usertype_constructor, subscript>;
@@ -494,8 +510,8 @@ using value_list = std::vector<managed_bytes>; // Sorted and deduped using value
/// never singular and never has start > end. Universal set is a nonwrapping_range with both bounds null.
using value_set = std::variant<value_list, nonwrapping_range<managed_bytes>>;
/// A set of all column values that would satisfy an expression. The _token_values variant finds
/// matching values for the partition token function call instead of the column.
/// A set of all column values that would satisfy an expression. If column is null, a set of all token values
/// that satisfy.
///
/// An expression restricts possible values of a column or token:
/// - `A>5` restricts A from below
@@ -505,8 +521,7 @@ using value_set = std::variant<value_list, nonwrapping_range<managed_bytes>>;
/// - `A=1 AND A<=0` restricts A to an empty list; no value is able to satisfy the expression
/// - `A>=NULL` also restricts A to an empty list; all comparisons to NULL are false
/// - an expression without A "restricts" A to unbounded range
extern value_set possible_column_values(const column_definition*, const expression&, const query_options&);
extern value_set possible_partition_token_values(const expression&, const query_options&, const schema& table_schema);
extern value_set possible_lhs_values(const column_definition*, const expression&, const query_options&);
/// Turns value_set into a range, unless it's a multi-valued list (in which case this throws).
extern nonwrapping_range<managed_bytes> to_range(const value_set&);
@@ -627,21 +642,8 @@ inline bool is_multi_column(const binary_operator& op) {
return expr::is<tuple_constructor>(op.lhs);
}
// Check whether the given expression represents
// a call to the token() function.
bool is_token_function(const function_call&);
bool is_token_function(const expression&);
bool is_partition_token_for_schema(const function_call&, const schema&);
bool is_partition_token_for_schema(const expression&, const schema&);
/// Check whether the expression contains a binary_operator whose LHS is a call to the token
/// function representing a partition key token.
/// Examples:
/// For expression: "token(p1, p2, p3) < 123 AND c = 2" returns true
/// For expression: "p1 = token(1, 2, 3) AND c = 2" return false
inline bool has_partition_token(const expression& e, const schema& table_schema) {
return find_binop(e, [&] (const binary_operator& o) { return is_partition_token_for_schema(o.lhs, table_schema); });
inline bool has_token(const expression& e) {
return find_binop(e, [] (const binary_operator& o) { return expr::is<token>(o.lhs); });
}
inline bool has_slice_or_needs_filtering(const expression& e) {
@@ -687,8 +689,7 @@ extern expression replace_column_def(const expression&, const column_definition*
// Replaces all occurences of token(p1, p2) on the left hand side with the given colum.
// For example this changes token(p1, p2) < token(1, 2) to my_column_name < token(1, 2).
// Schema is needed to find out which calls to token() describe the partition token.
extern expression replace_partition_token(const expression&, const column_definition*, const schema&);
extern expression replace_token(const expression&, const column_definition*);
// Recursively copies e and returns it. Calls replace_candidate() on all nodes. If it returns nullopt,
// continue with the copying. If it returns an expression, that expression replaces the current node.
@@ -828,12 +829,12 @@ bool has_only_eq_binops(const expression&);
} // namespace cql3
/// Custom formatter for an expression. Use {:user} for user-oriented
/// output, {:debug} for debug-oriented output. User is the default.
/// output, {:debug} for debug-oriented output. Debug is the default.
///
/// Required for fmt::join() to work on expression.
template <>
class fmt::formatter<cql3::expr::expression> {
bool _debug = false;
bool _debug = true;
private:
constexpr static bool try_match_and_advance(format_parse_context& ctx, std::string_view s) {
auto [ctx_end, s_end] = std::ranges::mismatch(ctx, s);

View File

@@ -79,7 +79,7 @@ static
void
usertype_constructor_validate_assignable_to(const usertype_constructor& u, data_dictionary::database db, const sstring& keyspace, const column_specification& receiver) {
if (!receiver.type->is_user_type()) {
throw exceptions::invalid_request_exception(format("Invalid user type literal for {} of type {}", *receiver.name, receiver.type->as_cql3_type()));
throw exceptions::invalid_request_exception(format("Invalid user type literal for {} of type {}", receiver.name, receiver.type->as_cql3_type()));
}
auto ut = static_pointer_cast<const user_type_impl>(receiver.type);
@@ -91,7 +91,7 @@ usertype_constructor_validate_assignable_to(const usertype_constructor& u, data_
const expression& value = u.elements.at(field);
auto&& field_spec = usertype_field_spec_of(receiver, i);
if (!assignment_testable::is_assignable(test_assignment(value, db, keyspace, *field_spec))) {
throw exceptions::invalid_request_exception(format("Invalid user type literal for {}: field {} is not of type {}", *receiver.name, field, field_spec->type->as_cql3_type()));
throw exceptions::invalid_request_exception(format("Invalid user type literal for {}: field {} is not of type {}", receiver.name, field, field_spec->type->as_cql3_type()));
}
}
}
@@ -314,7 +314,7 @@ set_validate_assignable_to(const collection_constructor& c, data_dictionary::dat
return;
}
throw exceptions::invalid_request_exception(format("Invalid set literal for {} of type {}", *receiver.name, receiver.type->as_cql3_type()));
throw exceptions::invalid_request_exception(format("Invalid set literal for {} of type {}", receiver.name, receiver.type->as_cql3_type()));
}
auto&& value_spec = set_value_spec_of(receiver);
@@ -502,18 +502,18 @@ void
tuple_constructor_validate_assignable_to(const tuple_constructor& tc, data_dictionary::database db, const sstring& keyspace, const column_specification& receiver) {
auto tt = dynamic_pointer_cast<const tuple_type_impl>(receiver.type->underlying_type());
if (!tt) {
throw exceptions::invalid_request_exception(format("Invalid tuple type literal for {} of type {}", *receiver.name, receiver.type->as_cql3_type()));
throw exceptions::invalid_request_exception(format("Invalid tuple type literal for {} of type {}", receiver.name, receiver.type->as_cql3_type()));
}
for (size_t i = 0; i < tc.elements.size(); ++i) {
if (i >= tt->size()) {
throw exceptions::invalid_request_exception(format("Invalid tuple literal for {}: too many elements. Type {} expects {:d} but got {:d}",
*receiver.name, tt->as_cql3_type(), tt->size(), tc.elements.size()));
receiver.name, tt->as_cql3_type(), tt->size(), tc.elements.size()));
}
auto&& value = tc.elements[i];
auto&& spec = component_spec_of(receiver, i);
if (!assignment_testable::is_assignable(test_assignment(value, db, keyspace, *spec))) {
throw exceptions::invalid_request_exception(format("Invalid tuple literal for {}: component {:d} is not of type {}", *receiver.name, i, spec->type->as_cql3_type()));
throw exceptions::invalid_request_exception(format("Invalid tuple literal for {}: component {:d} is not of type {}", receiver.name, i, spec->type->as_cql3_type()));
}
}
}
@@ -817,38 +817,17 @@ cast_prepare_expression(const cast& c, data_dictionary::database db, const sstri
std::optional<expression>
prepare_function_call(const expr::function_call& fc, data_dictionary::database db, const sstring& keyspace, const schema* schema_opt, lw_shared_ptr<column_specification> receiver) {
// Try to extract a column family name from the available information.
// Most functions can be prepared without information about the column family, usually just the keyspace is enough.
// One exception is the token() function - in order to prepare system.token() we have to know the partition key of the table,
// which can only be known when the column family is known.
// In cases when someone calls prepare_function_call on a token() function without a known column_family, an exception is thrown by functions::get.
std::optional<std::string_view> cf_name;
if (schema_opt != nullptr) {
cf_name = std::string_view(schema_opt->cf_name());
} else if (receiver.get() != nullptr) {
cf_name = receiver->cf_name;
if (!receiver) {
// TODO: It is possible to infer the type of a function call if there is only one overload, or if all overloads return the same type
return std::nullopt;
}
// Prepare the arguments that can be prepared without a receiver.
// Prepared expressions have a known type, which helps with finding the right function.
std::vector<expression> partially_prepared_args;
for (const expression& argument : fc.args) {
std::optional<expression> prepared_arg_opt = try_prepare_expression(argument, db, keyspace, schema_opt, nullptr);
if (prepared_arg_opt.has_value()) {
partially_prepared_args.emplace_back(*prepared_arg_opt);
} else {
partially_prepared_args.push_back(argument);
}
}
auto&& fun = std::visit(overloaded_functor{
[] (const shared_ptr<functions::function>& func) {
return func;
},
[&] (const functions::function_name& name) {
auto args = boost::copy_range<std::vector<::shared_ptr<assignment_testable>>>(
partially_prepared_args | boost::adaptors::transformed(expr::as_assignment_testable));
auto fun = functions::functions::get(db, keyspace, name, args, keyspace, cf_name, receiver.get());
auto args = boost::copy_range<std::vector<::shared_ptr<assignment_testable>>>(fc.args | boost::adaptors::transformed(expr::as_assignment_testable));
auto fun = functions::functions::get(db, keyspace, name, args, receiver->ks_name, receiver->cf_name, receiver.get());
if (!fun) {
throw exceptions::invalid_request_exception(format("Unknown function {} called", name));
}
@@ -864,7 +843,7 @@ prepare_function_call(const expr::function_call& fc, data_dictionary::database d
// Functions.get() will complain if no function "name" type check with the provided arguments.
// We still have to validate that the return type matches however
if (receiver && !receiver->type->is_value_compatible_with(*scalar_fun->return_type())) {
if (!receiver->type->is_value_compatible_with(*scalar_fun->return_type())) {
throw exceptions::invalid_request_exception(format("Type error: cannot assign result of function {} (type {}) to {} (type {})",
fun->name(), fun->return_type()->as_cql3_type(),
receiver->name, receiver->type->as_cql3_type()));
@@ -876,11 +855,11 @@ prepare_function_call(const expr::function_call& fc, data_dictionary::database d
}
std::vector<expr::expression> parameters;
parameters.reserve(partially_prepared_args.size());
parameters.reserve(fc.args.size());
bool all_terminal = true;
for (size_t i = 0; i < partially_prepared_args.size(); ++i) {
expr::expression e = prepare_expression(partially_prepared_args[i], db, keyspace, schema_opt,
functions::functions::make_arg_spec(keyspace, cf_name, *scalar_fun, i));
for (size_t i = 0; i < fc.args.size(); ++i) {
expr::expression e = prepare_expression(fc.args[i], db, keyspace, schema_opt,
functions::functions::make_arg_spec(receiver->ks_name, receiver->cf_name, *scalar_fun, i));
if (!expr::is<expr::constant>(e)) {
all_terminal = false;
}
@@ -929,17 +908,6 @@ test_assignment_function_call(const cql3::expr::function_call& fc, data_dictiona
}
}
static assignment_testable::test_result expression_test_assignment(const data_type& expr_type,
const column_specification& receiver) {
if (receiver.type->underlying_type() == expr_type->underlying_type()) {
return assignment_testable::test_result::EXACT_MATCH;
} else if (receiver.type->is_value_compatible_with(*expr_type)) {
return assignment_testable::test_result::WEAKLY_ASSIGNABLE;
} else {
return assignment_testable::test_result::NOT_ASSIGNABLE;
}
}
std::optional<expression> prepare_conjunction(const conjunction& conj,
data_dictionary::database db,
const sstring& keyspace,
@@ -990,20 +958,8 @@ std::optional<expression> prepare_conjunction(const conjunction& conj,
std::optional<expression>
try_prepare_expression(const expression& expr, data_dictionary::database db, const sstring& keyspace, const schema* schema_opt, lw_shared_ptr<column_specification> receiver) {
return expr::visit(overloaded_functor{
[&] (const constant& value) -> std::optional<expression> {
if (receiver && !is_assignable(expression_test_assignment(value.type, *receiver))) {
throw exceptions::invalid_request_exception(
format("cannot assign a constant {:user} of type {} to receiver {} of type {}", value,
value.type->as_cql3_type(), receiver->name, receiver->type->as_cql3_type()));
}
constant result = value;
if (receiver) {
// The receiver might have a different type from the constant, but this is allowed if the types are compatible.
// In such case the type is implictly converted to receiver type.
result.type = receiver->type;
}
return result;
[] (const constant&) -> std::optional<expression> {
on_internal_error(expr_logger, "Can't prepare constant_value, it should not appear in parser output");
},
[&] (const binary_operator& binop) -> std::optional<expression> {
if (receiver.get() != nullptr && &receiver->type->without_reversed() != boolean_type.get()) {
@@ -1057,6 +1013,24 @@ try_prepare_expression(const expression& expr, data_dictionary::database db, con
.type = static_cast<const collection_type_impl&>(sub_col_type).value_comparator(),
};
},
[&] (const token& tk) -> std::optional<expression> {
if (!schema_opt) {
throw exceptions::invalid_request_exception("cannot process token() function without schema");
}
std::vector<expression> prepared_token_args;
prepared_token_args.reserve(tk.args.size());
for (const expression& arg : tk.args) {
auto prepared_arg_opt = try_prepare_expression(arg, db, keyspace, schema_opt, receiver);
if (!prepared_arg_opt) {
return std::nullopt;
}
prepared_token_args.emplace_back(std::move(*prepared_arg_opt));
}
return token(std::move(prepared_token_args));
},
[&] (const unresolved_identifier& unin) -> std::optional<expression> {
if (!schema_opt) {
throw exceptions::invalid_request_exception(fmt::format("Cannot resolve column {} without schema", unin.ident->to_cql_string()));
@@ -1102,8 +1076,9 @@ assignment_testable::test_result
test_assignment(const expression& expr, data_dictionary::database db, const sstring& keyspace, const column_specification& receiver) {
using test_result = assignment_testable::test_result;
return expr::visit(overloaded_functor{
[&] (const constant& value) -> test_result {
return expression_test_assignment(value.type, receiver);
[&] (const constant&) -> test_result {
// constants shouldn't appear in parser output, only untyped_constants
on_internal_error(expr_logger, "constants are not yet reachable via test_assignment()");
},
[&] (const binary_operator&) -> test_result {
on_internal_error(expr_logger, "binary_operators are not yet reachable via test_assignment()");
@@ -1111,12 +1086,15 @@ test_assignment(const expression& expr, data_dictionary::database db, const sstr
[&] (const conjunction&) -> test_result {
on_internal_error(expr_logger, "conjunctions are not yet reachable via test_assignment()");
},
[&] (const column_value& col_val) -> test_result {
return expression_test_assignment(col_val.col->type, receiver);
[&] (const column_value&) -> test_result {
on_internal_error(expr_logger, "column_values are not yet reachable via test_assignment()");
},
[&] (const subscript&) -> test_result {
on_internal_error(expr_logger, "subscripts are not yet reachable via test_assignment()");
},
[&] (const token&) -> test_result {
on_internal_error(expr_logger, "tokens are not yet reachable via test_assignment()");
},
[&] (const unresolved_identifier&) -> test_result {
on_internal_error(expr_logger, "unresolved_identifiers are not yet reachable via test_assignment()");
},
@@ -1243,30 +1221,11 @@ static lw_shared_ptr<column_specification> get_lhs_receiver(const expression& pr
data_type tuple_type = tuple_type_impl::get_instance(tuple_types);
return make_lw_shared<column_specification>(schema.ks_name(), schema.cf_name(), std::move(identifier), std::move(tuple_type));
},
[&](const function_call& fun_call) -> lw_shared_ptr<column_specification> {
// In case of an expression like `token(p1, p2, p3) = ?` the receiver name should be "partition key token".
// This is required for compatibality with the java driver, it breaks with a receiver name like "token(p1, p2, p3)".
if (is_partition_token_for_schema(fun_call, schema)) {
return make_lw_shared<column_specification>(
schema.ks_name(),
schema.cf_name(),
::make_shared<column_identifier>("partition key token", true),
long_type);
}
data_type return_type = std::visit(
overloaded_functor{
[](const shared_ptr<db::functions::function>& fun) -> data_type { return fun->return_type(); },
[&](const functions::function_name&) -> data_type {
on_internal_error(expr_logger,
format("get_lhs_receiver: unprepared function call {:debug}", fun_call));
}},
fun_call.func);
return make_lw_shared<column_specification>(
schema.ks_name(), schema.cf_name(),
::make_shared<column_identifier>(format("{:user}", fun_call), true),
return_type);
[&](const token& col_val) -> lw_shared_ptr<column_specification> {
return make_lw_shared<column_specification>(schema.ks_name(),
schema.cf_name(),
::make_shared<column_identifier>("partition key token", true),
dht::token::get_token_validator());
},
[](const auto& other) -> lw_shared_ptr<column_specification> {
on_internal_error(expr_logger, format("get_lhs_receiver: unexpected expression: {}", other));

View File

@@ -152,10 +152,7 @@ void preliminary_binop_vaidation_checks(const binary_operator& binop) {
}
}
// Right now a token() on the LHS means that there's a partition token there.
// In the future with relaxed grammar this might no longer be true and this check will have to be revisisted.
// Moving the check after preparation would break tests and cassandra compatability.
if (is_token_function(binop.lhs)) {
if (is<token>(binop.lhs)) {
if (binop.op == oper_t::IN) {
throw exceptions::invalid_request_exception("IN cannot be used with the token function");
}
@@ -217,9 +214,9 @@ binary_operator validate_and_prepare_new_restriction(const binary_operator& rest
}
validate_multi_column_relation(lhs_cols, prepared_binop.op);
} else if (is_token_function(prepared_binop.lhs)) {
} else if (auto lhs_token = as_if<token>(&prepared_binop.lhs)) {
// Token restriction
std::vector<const column_definition*> column_defs = to_column_definitions(as<function_call>(prepared_binop.lhs).args);
std::vector<const column_definition*> column_defs = to_column_definitions(lhs_token->args);
validate_token_relation(column_defs, prepared_binop.op, *schema);
} else {
// Anything else

View File

@@ -202,10 +202,9 @@ std::optional<function_name> functions::used_by_user_function(const ut_name& use
}
lw_shared_ptr<column_specification>
functions::make_arg_spec(const sstring& receiver_ks, std::optional<const std::string_view> receiver_cf_opt,
functions::make_arg_spec(const sstring& receiver_ks, const sstring& receiver_cf,
const function& fun, size_t i) {
auto&& name = fmt::to_string(fun.name());
const std::string_view receiver_cf = receiver_cf_opt.has_value() ? *receiver_cf_opt : "<unknown_col_family>";
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
return make_lw_shared<column_specification>(receiver_ks,
receiver_cf,
@@ -323,7 +322,7 @@ functions::get(data_dictionary::database db,
const function_name& name,
const std::vector<shared_ptr<assignment_testable>>& provided_args,
const sstring& receiver_ks,
std::optional<const std::string_view> receiver_cf,
const sstring& receiver_cf,
const column_specification* receiver) {
static const function_name TOKEN_FUNCTION_NAME = function_name::native_function("token");
@@ -333,11 +332,7 @@ functions::get(data_dictionary::database db,
if (name.has_keyspace()
? name == TOKEN_FUNCTION_NAME
: name.name == TOKEN_FUNCTION_NAME.name) {
if (!receiver_cf.has_value()) {
throw exceptions::invalid_request_exception("functions::get for token doesn't have a known column family");
}
auto fun = ::make_shared<token_fct>(db.find_schema(receiver_ks, *receiver_cf));
auto fun = ::make_shared<token_fct>(db.find_schema(receiver_ks, receiver_cf));
validate_types(db, keyspace, fun, provided_args, receiver_ks, receiver_cf);
return fun;
}
@@ -509,7 +504,7 @@ functions::validate_types(data_dictionary::database db,
shared_ptr<function> fun,
const std::vector<shared_ptr<assignment_testable>>& provided_args,
const sstring& receiver_ks,
std::optional<const std::string_view> receiver_cf) {
const sstring& receiver_cf) {
if (provided_args.size() != fun->arg_types().size()) {
throw exceptions::invalid_request_exception(
format("Invalid number of arguments in call to function {}: {:d} required but {:d} provided",
@@ -539,7 +534,7 @@ functions::match_arguments(data_dictionary::database db, const sstring& keyspace
shared_ptr<function> fun,
const std::vector<shared_ptr<assignment_testable>>& provided_args,
const sstring& receiver_ks,
std::optional<const std::string_view> receiver_cf) {
const sstring& receiver_cf) {
if (provided_args.size() != fun->arg_types().size()) {
return assignment_testable::test_result::NOT_ASSIGNABLE;
}

View File

@@ -40,7 +40,7 @@ class functions {
private:
static std::unordered_multimap<function_name, shared_ptr<function>> init() noexcept;
public:
static lw_shared_ptr<column_specification> make_arg_spec(const sstring& receiver_ks, std::optional<const std::string_view> receiver_cf,
static lw_shared_ptr<column_specification> make_arg_spec(const sstring& receiver_ks, const sstring& receiver_cf,
const function& fun, size_t i);
public:
static shared_ptr<function> get(data_dictionary::database db,
@@ -48,7 +48,7 @@ public:
const function_name& name,
const std::vector<shared_ptr<assignment_testable>>& provided_args,
const sstring& receiver_ks,
std::optional<const std::string_view> receiver_cf,
const sstring& receiver_cf,
const column_specification* receiver = nullptr);
template <typename AssignmentTestablePtrRange>
static shared_ptr<function> get(data_dictionary::database db,
@@ -56,7 +56,7 @@ public:
const function_name& name,
AssignmentTestablePtrRange&& provided_args,
const sstring& receiver_ks,
std::optional<const std::string_view> receiver_cf,
const sstring& receiver_cf,
const column_specification* receiver = nullptr) {
const std::vector<shared_ptr<assignment_testable>> args(std::begin(provided_args), std::end(provided_args));
return get(db, keyspace, name, args, receiver_ks, receiver_cf, receiver);
@@ -87,12 +87,12 @@ private:
shared_ptr<function> fun,
const std::vector<shared_ptr<assignment_testable>>& provided_args,
const sstring& receiver_ks,
std::optional<const std::string_view> receiver_cf);
const sstring& receiver_cf);
static assignment_testable::test_result match_arguments(data_dictionary::database db, const sstring& keyspace,
shared_ptr<function> fun,
const std::vector<shared_ptr<assignment_testable>>& provided_args,
const sstring& receiver_ks,
std::optional<const std::string_view> receiver_cf);
const sstring& receiver_cf);
static bool type_equals(const std::vector<data_type>& t1, const std::vector<data_type>& t2);

View File

@@ -32,9 +32,9 @@ operation::set_element::prepare(data_dictionary::database db, const sstring& key
using exceptions::invalid_request_exception;
auto rtype = dynamic_pointer_cast<const collection_type_impl>(receiver.type);
if (!rtype) {
throw invalid_request_exception(format("Invalid operation ({}) for non collection column {}", to_string(receiver), receiver.name_as_text()));
throw invalid_request_exception(format("Invalid operation ({}) for non collection column {}", to_string(receiver), receiver.name()));
} else if (!rtype->is_multi_cell()) {
throw invalid_request_exception(format("Invalid operation ({}) for frozen collection column {}", to_string(receiver), receiver.name_as_text()));
throw invalid_request_exception(format("Invalid operation ({}) for frozen collection column {}", to_string(receiver), receiver.name()));
}
if (rtype->get_kind() == abstract_type::kind::list) {
@@ -47,7 +47,7 @@ operation::set_element::prepare(data_dictionary::database db, const sstring& key
return make_shared<lists::setter_by_index>(receiver, std::move(idx), std::move(lval));
}
} else if (rtype->get_kind() == abstract_type::kind::set) {
throw invalid_request_exception(format("Invalid operation ({}) for set column {}", to_string(receiver), receiver.name_as_text()));
throw invalid_request_exception(format("Invalid operation ({}) for set column {}", to_string(receiver), receiver.name()));
} else if (rtype->get_kind() == abstract_type::kind::map) {
auto key = prepare_expression(_selector, db, keyspace, nullptr, maps::key_spec_of(*receiver.column_specification));
auto mval = prepare_expression(_value, db, keyspace, nullptr, maps::value_spec_of(*receiver.column_specification));
@@ -136,11 +136,11 @@ operation::addition::prepare(data_dictionary::database db, const sstring& keyspa
auto ctype = dynamic_pointer_cast<const collection_type_impl>(receiver.type);
if (!ctype) {
if (!receiver.is_counter()) {
throw exceptions::invalid_request_exception(format("Invalid operation ({}) for non counter column {}", to_string(receiver), receiver.name_as_text()));
throw exceptions::invalid_request_exception(format("Invalid operation ({}) for non counter column {}", to_string(receiver), receiver.name()));
}
return make_shared<constants::adder>(receiver, std::move(v));
} else if (!ctype->is_multi_cell()) {
throw exceptions::invalid_request_exception(format("Invalid operation ({}) for frozen collection column {}", to_string(receiver), receiver.name_as_text()));
throw exceptions::invalid_request_exception(format("Invalid operation ({}) for frozen collection column {}", to_string(receiver), receiver.name()));
}
if (ctype->get_kind() == abstract_type::kind::list) {
@@ -169,14 +169,14 @@ operation::subtraction::prepare(data_dictionary::database db, const sstring& key
auto ctype = dynamic_pointer_cast<const collection_type_impl>(receiver.type);
if (!ctype) {
if (!receiver.is_counter()) {
throw exceptions::invalid_request_exception(format("Invalid operation ({}) for non counter column {}", to_string(receiver), receiver.name_as_text()));
throw exceptions::invalid_request_exception(format("Invalid operation ({}) for non counter column {}", to_string(receiver), receiver.name()));
}
auto v = prepare_expression(_value, db, keyspace, nullptr, receiver.column_specification);
return make_shared<constants::subtracter>(receiver, std::move(v));
}
if (!ctype->is_multi_cell()) {
throw exceptions::invalid_request_exception(
format("Invalid operation ({}) for frozen collection column {}", to_string(receiver), receiver.name_as_text()));
format("Invalid operation ({}) for frozen collection column {}", to_string(receiver), receiver.name()));
}
if (ctype->get_kind() == abstract_type::kind::list) {
@@ -211,9 +211,9 @@ operation::prepend::prepare(data_dictionary::database db, const sstring& keyspac
auto v = prepare_expression(_value, db, keyspace, nullptr, receiver.column_specification);
if (!dynamic_cast<const list_type_impl*>(receiver.type.get())) {
throw exceptions::invalid_request_exception(format("Invalid operation ({}) for non list column {}", to_string(receiver), receiver.name_as_text()));
throw exceptions::invalid_request_exception(format("Invalid operation ({}) for non list column {}", to_string(receiver), receiver.name()));
} else if (!receiver.type->is_multi_cell()) {
throw exceptions::invalid_request_exception(format("Invalid operation ({}) for frozen list column {}", to_string(receiver), receiver.name_as_text()));
throw exceptions::invalid_request_exception(format("Invalid operation ({}) for frozen list column {}", to_string(receiver), receiver.name()));
}
return make_shared<lists::prepender>(receiver, std::move(v));
@@ -296,6 +296,8 @@ operation::set_counter_value_from_tuple_list::prepare(data_dictionary::database
auto clock = value_cast<int64_t>(tuple[2]);
auto value = value_cast<int64_t>(tuple[3]);
using namespace std::rel_ops;
if (id <= last) {
throw marshal_exception(
format("invalid counter id order, {} <= {}",
@@ -341,9 +343,9 @@ operation::element_deletion::affected_column() const {
shared_ptr<operation>
operation::element_deletion::prepare(data_dictionary::database db, const sstring& keyspace, const column_definition& receiver) const {
if (!receiver.type->is_collection()) {
throw exceptions::invalid_request_exception(format("Invalid deletion operation for non collection column {}", receiver.name_as_text()));
throw exceptions::invalid_request_exception(format("Invalid deletion operation for non collection column {}", receiver.name()));
} else if (!receiver.type->is_multi_cell()) {
throw exceptions::invalid_request_exception(format("Invalid deletion operation for frozen collection column {}", receiver.name_as_text()));
throw exceptions::invalid_request_exception(format("Invalid deletion operation for frozen collection column {}", receiver.name()));
}
auto ctype = static_pointer_cast<const collection_type_impl>(receiver.type);
if (ctype->get_kind() == abstract_type::kind::list) {

View File

@@ -58,7 +58,13 @@ public:
return key.key().second;
}
bool operator==(const prepared_cache_key_type& other) const = default;
bool operator==(const prepared_cache_key_type& other) const {
return _key == other._key;
}
bool operator!=(const prepared_cache_key_type& other) const {
return !(*this == other);
}
};
class prepared_statements_cache {

View File

@@ -729,17 +729,65 @@ bool query_processor::has_more_results(::shared_ptr<cql3::internal_query_state>
return false;
}
future<> query_processor::for_each_cql_result(
::shared_ptr<cql3::internal_query_state> state,
std::function<stop_iteration(const cql3::untyped_result_set::row&)>&& f) {
return do_with(seastar::shared_ptr<bool>(), [f, this, state](auto& is_done) mutable {
is_done = seastar::make_shared<bool>(false);
auto stop_when = [is_done]() {
return *is_done;
};
auto do_resuls = [is_done, state, f, this]() mutable {
return this->execute_paged_internal(
state).then([is_done, state, f, this](::shared_ptr<cql3::untyped_result_set> msg) mutable {
if (msg->empty()) {
*is_done = true;
} else {
if (!this->has_more_results(state)) {
*is_done = true;
}
for (auto& row : *msg) {
if (f(row) == stop_iteration::yes) {
*is_done = true;
break;
}
}
}
});
};
return do_until(stop_when, do_resuls);
});
}
future<> query_processor::for_each_cql_result(
::shared_ptr<cql3::internal_query_state> state,
noncopyable_function<future<stop_iteration>(const cql3::untyped_result_set::row&)>&& f) {
do {
auto msg = co_await execute_paged_internal(state);
for (auto& row : *msg) {
if ((co_await f(row)) == stop_iteration::yes) {
co_return;
}
}
} while (has_more_results(state));
// repeat can move the lambda's capture, so we need to hold f and it so the internal loop
// will be able to use it.
return do_with(noncopyable_function<future<stop_iteration>(const cql3::untyped_result_set::row&)>(std::move(f)),
untyped_result_set::rows_type::const_iterator(),
[state, this](noncopyable_function<future<stop_iteration>(const cql3::untyped_result_set::row&)>& f,
untyped_result_set::rows_type::const_iterator& it) mutable {
return repeat([state, &f, &it, this]() mutable {
return this->execute_paged_internal(state).then([state, &f, &it, this](::shared_ptr<cql3::untyped_result_set> msg) mutable {
it = msg->begin();
return repeat_until_value([&it, &f, msg, state, this]() mutable {
if (it == msg->end()) {
return make_ready_future<std::optional<stop_iteration>>(std::optional<stop_iteration>(!this->has_more_results(state)));
}
return f(*it).then([&it, msg](stop_iteration i) {
if (i == stop_iteration::yes) {
return std::optional<stop_iteration>(i);
}
++it;
return std::optional<stop_iteration>();
});
});
});
});
});
}
future<::shared_ptr<untyped_result_set>>
@@ -900,9 +948,6 @@ void query_processor::migration_subscriber::on_update_view(
const sstring& view_name, bool columns_changed) {
}
void query_processor::migration_subscriber::on_update_tablet_metadata() {
}
void query_processor::migration_subscriber::on_drop_keyspace(const sstring& ks_name) {
remove_invalid_prepared_statements(ks_name, std::nullopt);
}

View File

@@ -294,8 +294,6 @@ public:
* page_size - maximum page size
* f - a function to be run on each row of the query result,
* if the function returns stop_iteration::yes the iteration will stop
*
* \note This function is optimized for convenience, not performance.
*/
future<> query_internal(
const sstring& query_string,
@@ -312,8 +310,6 @@ public:
* query_string - the cql string, can contain placeholders
* f - a function to be run on each row of the query result,
* if the function returns stop_iteration::yes the iteration will stop
*
* \note This function is optimized for convenience, not performance.
*/
future<> query_internal(
const sstring& query_string,
@@ -328,8 +324,6 @@ public:
// and schema changes will not be announced to other nodes.
// Because of that, changing global schema state (e.g. modifying non-local tables,
// creating namespaces, etc) is explicitly forbidden via this interface.
//
// note: optimized for convenience, not performance.
future<::shared_ptr<untyped_result_set>> execute_internal(
const sstring& query_string,
db::consistency_level,
@@ -435,15 +429,18 @@ private:
/*!
* \brief run a query using paging
*
* \note Optimized for convenience, not performance.
*/
future<::shared_ptr<untyped_result_set>> execute_paged_internal(::shared_ptr<internal_query_state> state);
/*!
* \brief iterate over all results using paging
*/
future<> for_each_cql_result(
::shared_ptr<cql3::internal_query_state> state,
std::function<stop_iteration(const cql3::untyped_result_set_row&)>&& f);
/*!
* \brief iterate over all results using paging, accept a function that returns a future
*
* \note Optimized for convenience, not performance.
*/
future<> for_each_cql_result(
::shared_ptr<cql3::internal_query_state> state,
@@ -525,7 +522,6 @@ public:
virtual void on_update_function(const sstring& ks_name, const sstring& function_name) override;
virtual void on_update_aggregate(const sstring& ks_name, const sstring& aggregate_name) override;
virtual void on_update_view(const sstring& ks_name, const sstring& view_name, bool columns_changed) override;
virtual void on_update_tablet_metadata() override;
virtual void on_drop_keyspace(const sstring& ks_name) override;
virtual void on_drop_column_family(const sstring& ks_name, const sstring& cf_name) override;

View File

@@ -88,8 +88,7 @@ void with_current_binary_operator(
static std::vector<expr::expression> extract_partition_range(
const expr::expression& where_clause, schema_ptr schema) {
using namespace expr;
struct extract_partition_range_visitor {
schema_ptr table_schema;
struct {
std::optional<expression> tokens;
std::unordered_map<const column_definition*, expression> single_column;
const binary_operator* current_binary_operator = nullptr;
@@ -107,11 +106,7 @@ static std::vector<expr::expression> extract_partition_range(
current_binary_operator = nullptr;
}
void operator()(const function_call& token_fun_call) {
if (!is_partition_token_for_schema(token_fun_call, *table_schema)) {
on_internal_error(rlogger, "extract_partition_range(function_call)");
}
void operator()(const token&) {
with_current_binary_operator(*this, [&] (const binary_operator& b) {
if (tokens) {
tokens = make_conjunction(std::move(*tokens), b);
@@ -164,6 +159,10 @@ static std::vector<expr::expression> extract_partition_range(
on_internal_error(rlogger, "extract_partition_range(column_mutation_attribute)");
}
void operator()(const function_call&) {
on_internal_error(rlogger, "extract_partition_range(function_call)");
}
void operator()(const cast&) {
on_internal_error(rlogger, "extract_partition_range(cast)");
}
@@ -187,12 +186,7 @@ static std::vector<expr::expression> extract_partition_range(
void operator()(const usertype_constructor&) {
on_internal_error(rlogger, "extract_partition_range(usertype_constructor)");
}
};
extract_partition_range_visitor v {
.table_schema = schema
};
} v;
expr::visit(v, where_clause);
if (v.tokens) {
return {std::move(*v.tokens)};
@@ -213,7 +207,6 @@ static std::vector<expr::expression> extract_clustering_prefix_restrictions(
/// Collects all clustering-column restrictions from an expression. Presumes the expression only uses
/// conjunction to combine subexpressions.
struct visitor {
schema_ptr table_schema;
std::vector<expression> multi; ///< All multi-column restrictions.
/// All single-clustering-column restrictions, grouped by column. Each value is either an atom or a
/// conjunction of atoms.
@@ -273,13 +266,8 @@ static std::vector<expr::expression> extract_clustering_prefix_restrictions(
});
}
void operator()(const function_call& fun_call) {
if (is_partition_token_for_schema(fun_call, *table_schema)) {
// A token cannot be a clustering prefix restriction
return;
}
on_internal_error(rlogger, "extract_clustering_prefix_restrictions(function_call)");
void operator()(const token&) {
// A token cannot be a clustering prefix restriction
}
void operator()(const constant&) {}
@@ -292,6 +280,10 @@ static std::vector<expr::expression> extract_clustering_prefix_restrictions(
on_internal_error(rlogger, "extract_clustering_prefix_restrictions(column_mutation_attribute)");
}
void operator()(const function_call&) {
on_internal_error(rlogger, "extract_clustering_prefix_restrictions(function_call)");
}
void operator()(const cast&) {
on_internal_error(rlogger, "extract_clustering_prefix_restrictions(cast)");
}
@@ -315,11 +307,7 @@ static std::vector<expr::expression> extract_clustering_prefix_restrictions(
void operator()(const usertype_constructor&) {
on_internal_error(rlogger, "extract_clustering_prefix_restrictions(usertype_constructor)");
}
};
visitor v {
.table_schema = schema
};
} v;
expr::visit(v, where_clause);
if (!v.multi.empty()) {
@@ -370,7 +358,7 @@ statement_restrictions::statement_restrictions(data_dictionary::database db,
}
}
if (_where.has_value()) {
if (!has_token_restrictions()) {
if (!has_token(_partition_key_restrictions)) {
_single_column_partition_key_restrictions = expr::get_single_column_restrictions_map(_partition_key_restrictions);
}
if (!expr::contains_multi_column_restriction(_clustering_columns_restrictions)) {
@@ -500,7 +488,7 @@ std::pair<std::optional<secondary_index::index>, expr::expression> statement_res
for (const auto& index : sim.list_indexes()) {
auto cdef = _schema->get_column_definition(to_bytes(index.target_column()));
for (const expr::expression& restriction : index_restrictions()) {
if (has_partition_token(restriction, *_schema) || contains_multi_column_restriction(restriction)) {
if (has_token(restriction) || contains_multi_column_restriction(restriction)) {
continue;
}
@@ -528,8 +516,7 @@ bool statement_restrictions::has_eq_restriction_on_column(const column_definitio
std::vector<const column_definition*> statement_restrictions::get_column_defs_for_filtering(data_dictionary::database db) const {
std::vector<const column_definition*> column_defs_for_filtering;
if (need_filtering()) {
auto cf = db.find_column_family(_schema);
auto& sim = cf.get_index_manager();
auto& sim = db.find_column_family(_schema).get_index_manager();
auto opt_idx = std::get<0>(find_idx(sim));
auto column_uses_indexing = [&opt_idx] (const column_definition* cdef, const expr::expression* single_col_restr) {
return opt_idx && single_col_restr && is_supported_by(*single_col_restr, *opt_idx);
@@ -579,7 +566,7 @@ void statement_restrictions::add_restriction(const expr::binary_operator& restr,
} else if (expr::is_multi_column(restr)) {
// Multi column restrictions are only allowed on clustering columns
add_multi_column_clustering_key_restriction(restr);
} else if (has_partition_token(restr, *_schema)) {
} else if (has_token(restr)) {
// Token always restricts the partition key
add_token_partition_key_restriction(restr);
} else if (expr::is_single_column_restriction(restr)) {
@@ -623,7 +610,7 @@ void statement_restrictions::add_single_column_parition_key_restriction(const ex
"Only EQ and IN relation are supported on the partition key "
"(unless you use the token() function or allow filtering)");
}
if (has_token_restrictions()) {
if (has_token(_partition_key_restrictions)) {
throw exceptions::invalid_request_exception(
format("Columns \"{}\" cannot be restricted by both a normal relation and a token relation",
fmt::join(expr::get_sorted_column_defs(_partition_key_restrictions) |
@@ -638,7 +625,7 @@ void statement_restrictions::add_single_column_parition_key_restriction(const ex
}
void statement_restrictions::add_token_partition_key_restriction(const expr::binary_operator& restr) {
if (!partition_key_restrictions_is_empty() && !has_token_restrictions()) {
if (!partition_key_restrictions_is_empty() && !has_token(_partition_key_restrictions)) {
throw exceptions::invalid_request_exception(
format("Columns \"{}\" cannot be restricted by both a normal relation and a token relation",
fmt::join(expr::get_sorted_column_defs(_partition_key_restrictions) |
@@ -749,7 +736,7 @@ void statement_restrictions::process_partition_key_restrictions(bool for_view, b
// - Is it queriable without 2ndary index, which is always more efficient
// If a component of the partition key is restricted by a relation, all preceding
// components must have a EQ. Only the last partition key component can be in IN relation.
if (has_token_restrictions()) {
if (has_token(_partition_key_restrictions)) {
_is_key_range = true;
} else if (expr::is_empty_restriction(_partition_key_restrictions)) {
_is_key_range = true;
@@ -788,7 +775,7 @@ size_t statement_restrictions::partition_key_restrictions_size() const {
bool statement_restrictions::pk_restrictions_need_filtering() const {
return !expr::is_empty_restriction(_partition_key_restrictions)
&& !has_token_restrictions()
&& !has_token(_partition_key_restrictions)
&& (has_partition_key_unrestricted_components() || expr::has_slice_or_needs_filtering(_partition_key_restrictions));
}
@@ -899,7 +886,7 @@ bounds_slice statement_restrictions::get_clustering_slice() const {
bool statement_restrictions::parition_key_restrictions_have_supporting_index(const secondary_index::secondary_index_manager& index_manager,
expr::allow_local_index allow_local) const {
// Token restrictions can't be supported by an index
if (has_token_restrictions()) {
if (has_token(_partition_key_restrictions)) {
return false;
}
@@ -939,10 +926,8 @@ namespace {
using namespace expr;
/// Computes partition-key ranges from token atoms in ex.
dht::partition_range_vector partition_ranges_from_token(const expr::expression& ex,
const query_options& options,
const schema& table_schema) {
auto values = possible_partition_token_values(ex, options, table_schema);
dht::partition_range_vector partition_ranges_from_token(const expr::expression& ex, const query_options& options) {
auto values = possible_lhs_values(nullptr, ex, options);
if (values == expr::value_set(expr::value_list{})) {
return {};
}
@@ -990,7 +975,7 @@ dht::partition_range_vector partition_ranges_from_singles(
for (const auto& e : expressions) {
if (const auto arbitrary_binop = find_binop(e, [] (const binary_operator&) { return true; })) {
if (auto cv = expr::as_if<expr::column_value>(&arbitrary_binop->lhs)) {
const value_set vals = possible_column_values(cv->col, e, options);
const value_set vals = possible_lhs_values(cv->col, e, options);
if (auto lst = std::get_if<value_list>(&vals)) {
if (lst->empty()) {
return {};
@@ -1019,7 +1004,7 @@ dht::partition_range_vector partition_ranges_from_EQs(
std::vector<managed_bytes> pk_value(schema.partition_key_size());
for (const auto& e : eq_expressions) {
const auto col = expr::get_subscripted_column(find(e, oper_t::EQ)->lhs).col;
const auto vals = std::get<value_list>(possible_column_values(col, e, options));
const auto vals = std::get<value_list>(possible_lhs_values(col, e, options));
if (vals.empty()) { // Case of C=1 AND C=2.
return {};
}
@@ -1034,13 +1019,13 @@ dht::partition_range_vector statement_restrictions::get_partition_key_ranges(con
if (_partition_range_restrictions.empty()) {
return {dht::partition_range::make_open_ended_both_sides()};
}
if (has_partition_token(_partition_range_restrictions[0], *_schema)) {
if (has_token(_partition_range_restrictions[0])) {
if (_partition_range_restrictions.size() != 1) {
on_internal_error(
rlogger,
format("Unexpected size of token restrictions: {}", _partition_range_restrictions.size()));
}
return partition_ranges_from_token(_partition_range_restrictions[0], options, *_schema);
return partition_ranges_from_token(_partition_range_restrictions[0], options);
} else if (_partition_range_is_simple) {
// Special case to avoid extra allocations required for a Cartesian product.
return partition_ranges_from_EQs(_partition_range_restrictions, options, *_schema);
@@ -1248,6 +1233,10 @@ struct multi_column_range_accumulator {
on_internal_error(rlogger, "Subscript encountered outside binary operator");
}
void operator()(const token&) {
on_internal_error(rlogger, "Token encountered outside binary operator");
}
void operator()(const unresolved_identifier&) {
on_internal_error(rlogger, "Unresolved identifier encountered outside binary operator");
}
@@ -1351,7 +1340,7 @@ std::vector<query::clustering_range> get_single_column_clustering_bounds(
size_t product_size = 1;
std::vector<std::vector<managed_bytes>> prior_column_values; // Equality values of columns seen so far.
for (size_t i = 0; i < single_column_restrictions.size(); ++i) {
auto values = possible_column_values(
auto values = possible_lhs_values(
&schema.clustering_column_at(i), // This should be the LHS of restrictions[i].
single_column_restrictions[i],
options);
@@ -1421,10 +1410,10 @@ static std::vector<query::clustering_range> get_index_v1_token_range_clustering_
const column_definition& token_column,
const expression& token_restriction) {
// A workaround in order to make possible_column_values work properly.
// possible_column_values looks at the column type and uses this type's comparator.
// A workaround in order to make possible_lhs_values work properly.
// possible_lhs_values looks at the column type and uses this type's comparator.
// This is a problem because when using blob's comparator, -4 is greater than 4.
// This makes possible_column_values think that an expression like token(p) > -4 and token(p) < 4
// This makes possible_lhs_values think that an expression like token(p) > -4 and token(p) < 4
// is impossible to fulfill.
// Create a fake token column with the type set to bigint, translate the restriction to use this column
// and use this restriction to calculate possible lhs values.
@@ -1433,7 +1422,7 @@ static std::vector<query::clustering_range> get_index_v1_token_range_clustering_
expression new_token_restrictions = replace_column_def(token_restriction, &token_column_bigint);
std::variant<value_list, nonwrapping_range<managed_bytes>> values =
possible_column_values(&token_column_bigint, new_token_restrictions, options);
possible_lhs_values(&token_column_bigint, new_token_restrictions, options);
return std::visit(overloaded_functor {
[](const value_list& list) {
@@ -1701,7 +1690,7 @@ bool token_known(const statement_restrictions& r) {
bool statement_restrictions::need_filtering() const {
using namespace expr;
if (_uses_secondary_indexing && has_token_restrictions()) {
if (_uses_secondary_indexing && has_token(_partition_key_restrictions)) {
// If there is a token(p1, p2) restriction, no p1, p2 restrictions are allowed in the query.
// All other restrictions must be on clustering or regular columns.
int64_t non_pk_restrictions_count = clustering_columns_restrictions_size();
@@ -1798,11 +1787,11 @@ void statement_restrictions::prepare_indexed_global(const schema& idx_tbl_schema
const column_definition* token_column = &idx_tbl_schema.clustering_column_at(0);
if (has_token_restrictions()) {
if (has_token(_partition_key_restrictions)) {
// When there is a token(p1, p2) >/</= ? restriction, it is not allowed to have restrictions on p1 or p2.
// This means that p1 and p2 can have many different values (token is a hash, can have collisions).
// Clustering prefix ends after token_restriction, all further restrictions have to be filtered.
expr::expression token_restriction = replace_partition_token(_partition_key_restrictions, token_column, *_schema);
expr::expression token_restriction = replace_token(_partition_key_restrictions, token_column);
_idx_tbl_ck_prefix = std::vector{std::move(token_restriction)};
return;
@@ -1910,7 +1899,7 @@ std::vector<query::clustering_range> statement_restrictions::get_global_index_cl
std::vector<managed_bytes> pk_value(_schema->partition_key_size());
for (const auto& e : _partition_range_restrictions) {
const auto col = expr::as<column_value>(find(e, oper_t::EQ)->lhs).col;
const auto vals = std::get<value_list>(possible_column_values(col, e, options));
const auto vals = std::get<value_list>(possible_lhs_values(col, e, options));
if (vals.empty()) { // Case of C=1 AND C=2.
return {};
}

View File

@@ -181,7 +181,7 @@ public:
}
bool has_token_restrictions() const {
return has_partition_token(_partition_key_restrictions, *_schema);
return has_token(_partition_key_restrictions);
}
// Checks whether the given column has an EQ restriction.
@@ -478,7 +478,7 @@ public:
// If token restrictions are present in an indexed query, then all other restrictions need to be filtered.
// A single token restriction can have multiple matching partition key values.
// Because of this we can't create a clustering prefix with more than token restriction.
|| (_uses_secondary_indexing && has_token_restrictions());
|| (_uses_secondary_indexing && has_token(_partition_key_restrictions));
}
bool clustering_key_restrictions_need_filtering() const;

View File

@@ -10,7 +10,6 @@
#include "selection/selection.hh"
#include "stats.hh"
#include "utils/buffer_view-to-managed_bytes_view.hh"
namespace cql3 {
class untyped_result_set;
@@ -35,10 +34,10 @@ private:
private:
void accept_cell_value(const column_definition& def, query::result_row_view::iterator_type& i) {
if (def.is_multi_cell()) {
_visitor.accept_value(utils::buffer_view_to_managed_bytes_view(i.next_collection_cell()));
_visitor.accept_value(i.next_collection_cell());
} else {
auto cell = i.next_atomic_cell();
_visitor.accept_value(cell ? utils::buffer_view_to_managed_bytes_view(cell->value()) : managed_bytes_view_opt());
_visitor.accept_value(cell ? std::optional<query::result_bytes_view>(cell->value()) : std::optional<query::result_bytes_view>());
}
}
public:
@@ -66,11 +65,11 @@ private:
for (auto&& def : _selection.get_columns()) {
switch (def->kind) {
case column_kind::partition_key:
_visitor.accept_value(bytes_view(_partition_key[def->component_index()]));
_visitor.accept_value(query::result_bytes_view(bytes_view(_partition_key[def->component_index()])));
break;
case column_kind::clustering_key:
if (_clustering_key.size() > def->component_index()) {
_visitor.accept_value(bytes_view(_clustering_key[def->component_index()]));
_visitor.accept_value(query::result_bytes_view(bytes_view(_clustering_key[def->component_index()])));
} else {
_visitor.accept_value(std::nullopt);
}
@@ -93,7 +92,7 @@ private:
auto static_row_iterator = static_row.iterator();
for (auto&& def : _selection.get_columns()) {
if (def->is_partition_key()) {
_visitor.accept_value(bytes_view(_partition_key[def->component_index()]));
_visitor.accept_value(query::result_bytes_view(bytes_view(_partition_key[def->component_index()])));
} else if (def->is_static()) {
accept_cell_value(*def, static_row_iterator);
} else {

View File

@@ -113,23 +113,14 @@ bool result_set::empty() const {
return _rows.empty();
}
void result_set::add_row(std::vector<managed_bytes_opt> row) {
void result_set::add_row(std::vector<bytes_opt> row) {
assert(row.size() == _metadata->value_count());
_rows.emplace_back(std::move(row));
}
void result_set::add_row(std::vector<bytes_opt> row) {
row_type new_row;
new_row.reserve(row.size());
for (auto& bo : row) {
new_row.emplace_back(bo ? managed_bytes_opt(*bo) : managed_bytes_opt());
}
add_row(std::move(new_row));
}
void result_set::add_column_value(managed_bytes_opt value) {
void result_set::add_column_value(bytes_opt value) {
if (_rows.empty() || _rows.back().size() == _metadata->value_count()) {
std::vector<managed_bytes_opt> row;
std::vector<bytes_opt> row;
row.reserve(_metadata->value_count());
_rows.emplace_back(std::move(row));
}
@@ -137,10 +128,6 @@ void result_set::add_column_value(managed_bytes_opt value) {
_rows.back().emplace_back(std::move(value));
}
void result_set::add_column_value(bytes_opt value) {
add_column_value(to_managed_bytes_opt(value));
}
void result_set::reverse() {
std::reverse(_rows.begin(), _rows.end());
}
@@ -159,7 +146,7 @@ const metadata& result_set::get_metadata() const {
return *_metadata;
}
const utils::chunked_vector<std::vector<managed_bytes_opt>>& result_set::rows() const {
const utils::chunked_vector<std::vector<bytes_opt>>& result_set::rows() const {
return _rows;
}

View File

@@ -129,14 +129,14 @@ public:
};
template<typename Visitor>
concept ResultVisitor = requires(Visitor& visitor, managed_bytes_view_opt val) {
concept ResultVisitor = requires(Visitor& visitor) {
visitor.start_row();
visitor.accept_value(std::move(val));
visitor.accept_value(std::optional<query::result_bytes_view>());
visitor.end_row();
};
class result_set {
using col_type = managed_bytes_opt;
using col_type = bytes_opt;
using row_type = std::vector<col_type>;
using rows_type = utils::chunked_vector<row_type>;
@@ -157,10 +157,8 @@ public:
bool empty() const;
void add_row(row_type row);
void add_row(std::vector<bytes_opt> row);
void add_column_value(col_type value);
void add_column_value(bytes_opt value);
void reverse();
@@ -189,7 +187,7 @@ public:
visitor.start_row();
for (auto i = 0u; i < column_count; i++) {
auto& cell = row[i];
visitor.accept_value(cell ? managed_bytes_view_opt(*cell) : managed_bytes_view_opt());
visitor.accept_value(cell ? std::optional<query::result_bytes_view>(*cell) : std::optional<query::result_bytes_view>());
}
visitor.end_row();
}
@@ -206,12 +204,12 @@ public:
: _result(std::move(mtd)) { }
void start_row() { }
void accept_value(managed_bytes_view_opt value) {
void accept_value(std::optional<query::result_bytes_view> value) {
if (!value) {
_current_row.emplace_back();
return;
}
_current_row.emplace_back(value);
_current_row.emplace_back(value->linearize());
}
void end_row() {
_result.add_row(std::exchange(_current_row, { }));

View File

@@ -31,17 +31,16 @@ public:
for (size_t i = 0; i < m; ++i) {
auto&& s = _arg_selectors[i];
s->add_input(rs);
_args[i + 1] = to_bytes_opt(s->get_output());
_args[i + 1] = s->get_output();
s->reset();
}
_accumulator = _aggregate.aggregation_function->execute(_args);
}
virtual managed_bytes_opt get_output() override {
return to_managed_bytes_opt(
_aggregate.state_to_result_function
virtual bytes_opt get_output() override {
return _aggregate.state_to_result_function
? _aggregate.state_to_result_function->execute(std::span(&_accumulator, 1))
: std::move(_accumulator));
: std::move(_accumulator);
}
virtual void reset() override {

View File

@@ -62,12 +62,12 @@ public:
_selected->add_input(rs);
}
virtual managed_bytes_opt get_output() override {
virtual bytes_opt get_output() override {
auto&& value = _selected->get_output();
if (!value) {
return std::nullopt;
}
return get_nth_tuple_element(managed_bytes_view(*value), _field);
return get_nth_tuple_element(single_fragmented_view(*value), _field);
}
virtual data_type get_type() const override {

View File

@@ -38,14 +38,14 @@ public:
virtual void reset() override {
}
virtual managed_bytes_opt get_output() override {
virtual bytes_opt get_output() override {
size_t m = _arg_selectors.size();
for (size_t i = 0; i < m; ++i) {
auto&& s = _arg_selectors[i];
_args[i] = to_bytes_opt(s->get_output());
_args[i] = s->get_output();
s->reset();
}
return to_managed_bytes_opt(fun()->execute(_args));
return fun()->execute(_args);
}
virtual bool requires_thread() const override;

View File

@@ -173,6 +173,18 @@ prepare_selectable(const schema& s, const expr::expression& raw_selectable) {
[&] (const expr::subscript& sub) -> shared_ptr<selectable> {
on_internal_error(slogger, "no way to express 'SELECT a[b]' in the grammar yet");
},
[&] (const expr::token& tok) -> shared_ptr<selectable> {
// expr::token implicitly the partition key as arguments, but
// the selectable equivalent (with_function) needs explicit arguments,
// so construct them here.
auto name = functions::function_name("system", "token");
auto args = boost::copy_range<std::vector<shared_ptr<selectable>>>(
s.partition_key_columns()
| boost::adaptors::transformed([&] (const column_definition& cdef) {
return ::make_shared<selectable_column>(column_identifier(cdef.name(), cdef.name_as_text()));
}));
return ::make_shared<selectable::with_function>(std::move(name), std::move(args));
},
[&] (const expr::unresolved_identifier& ui) -> shared_ptr<selectable> {
return make_shared<selectable_column>(*ui.ident->prepare(s));
},
@@ -248,6 +260,11 @@ selectable_processes_selection(const expr::expression& raw_selectable) {
// so bridge them.
return false;
},
[&] (const expr::token&) -> bool {
// Arguably, should return false, because it only processes the partition key.
// But selectable::with_function considers it true now, so return that.
return true;
},
[&] (const expr::unresolved_identifier& ui) -> bool {
return ui.ident->processes_selection();
},

View File

@@ -122,7 +122,7 @@ public:
protected:
class simple_selectors : public selectors {
private:
std::vector<managed_bytes_opt> _current;
std::vector<bytes_opt> _current;
bool _first = true; ///< Whether the next row we receive is the first in its group.
public:
virtual void reset() override {
@@ -132,7 +132,7 @@ protected:
virtual bool requires_thread() const override { return false; }
virtual std::vector<managed_bytes_opt> get_output_row() override {
virtual std::vector<bytes_opt> get_output_row() override {
return std::move(_current);
}
@@ -234,8 +234,8 @@ protected:
return _factories->does_aggregation();
}
virtual std::vector<managed_bytes_opt> get_output_row() override {
std::vector<managed_bytes_opt> output_row;
virtual std::vector<bytes_opt> get_output_row() override {
std::vector<bytes_opt> output_row;
output_row.reserve(_selectors.size());
for (auto&& s : _selectors) {
output_row.emplace_back(s->get_output());

View File

@@ -52,7 +52,7 @@ public:
*/
virtual void add_input_row(result_set_builder& rs) = 0;
virtual std::vector<managed_bytes_opt> get_output_row() = 0;
virtual std::vector<bytes_opt> get_output_row() = 0;
virtual void reset() = 0;
};
@@ -189,10 +189,10 @@ private:
std::unique_ptr<result_set> _result_set;
std::unique_ptr<selectors> _selectors;
const std::vector<size_t> _group_by_cell_indices; ///< Indices in \c current of cells holding GROUP BY values.
std::vector<managed_bytes_opt> _last_group; ///< Previous row's group: all of GROUP BY column values.
std::vector<bytes_opt> _last_group; ///< Previous row's group: all of GROUP BY column values.
bool _group_began; ///< Whether a group began being formed.
public:
std::optional<std::vector<managed_bytes_opt>> current;
std::optional<std::vector<bytes_opt>> current;
private:
std::vector<api::timestamp_type> _timestamps;
std::vector<int32_t> _ttls;

View File

@@ -49,7 +49,7 @@ public:
* @return the selector output
* @throws InvalidRequestException if a problem occurs while computing the output value
*/
virtual managed_bytes_opt get_output() = 0;
virtual bytes_opt get_output() = 0;
/**
* Returns the <code>selector</code> output type.

View File

@@ -49,7 +49,7 @@ private:
const sstring _column_name;
const uint32_t _idx;
data_type _type;
managed_bytes_opt _current;
bytes_opt _current;
bool _first; ///< Whether the next row we receive is the first in its group.
public:
static ::shared_ptr<factory> new_factory(const sstring& column_name, uint32_t idx, data_type type) {
@@ -74,7 +74,7 @@ public:
}
}
virtual managed_bytes_opt get_output() override {
virtual bytes_opt get_output() override {
return std::move(_current);
}

View File

@@ -21,7 +21,7 @@ class writetime_or_ttl_selector : public selector {
sstring _column_name;
int _idx;
bool _is_writetime;
managed_bytes_opt _current;
bytes_opt _current;
public:
static shared_ptr<selector::factory> new_factory(sstring column_name, int idx, bool is_writetime) {
class wtots_factory : public selector::factory {
@@ -60,27 +60,25 @@ public:
if (_is_writetime) {
int64_t ts = rs.timestamp_of(_idx);
if (ts != api::missing_timestamp) {
auto tmp = bytes(bytes::initialized_later(), 8);
auto i = tmp.begin();
_current = bytes(bytes::initialized_later(), 8);
auto i = _current->begin();
serialize_int64(i, ts);
_current = managed_bytes(tmp);
} else {
_current = std::nullopt;
}
} else {
int ttl = rs.ttl_of(_idx);
if (ttl > 0) {
auto tmp = bytes(bytes::initialized_later(), 4);
auto i = tmp.begin();
_current = bytes(bytes::initialized_later(), 4);
auto i = _current->begin();
serialize_int32(i, ttl);
_current = managed_bytes(tmp);
} else {
_current = std::nullopt;
}
}
}
virtual managed_bytes_opt get_output() override {
virtual bytes_opt get_output() override {
return _current;
}

View File

@@ -60,14 +60,14 @@ future<std::vector<mutation>> alter_type_statement::prepare_announcement_mutatio
auto to_update = all_types.find(_name.get_user_type_name());
// Shouldn't happen, unless we race with a drop
if (to_update == all_types.end()) {
throw exceptions::invalid_request_exception(format("No user type named {} exists.", _name.to_cql_string()));
throw exceptions::invalid_request_exception(format("No user type named {} exists.", _name.to_string()));
}
for (auto&& schema : ks.metadata()->cf_meta_data() | boost::adaptors::map_values) {
for (auto&& column : schema->partition_key_columns()) {
if (column.type->references_user_type(_name.get_keyspace(), _name.get_user_type_name())) {
throw exceptions::invalid_request_exception(format("Cannot add new field to type {} because it is used in the partition key column {} of table {}.{}",
_name.to_cql_string(), column.name_as_text(), schema->ks_name(), schema->cf_name()));
_name.to_string(), column.name_as_text(), schema->ks_name(), schema->cf_name()));
}
}
}
@@ -134,7 +134,7 @@ user_type alter_type_statement::add_or_alter::do_add(data_dictionary::database d
{
if (to_update->idx_of_field(_field_name->name())) {
throw exceptions::invalid_request_exception(format("Cannot add new field {} to type {}: a field of the same name already exists",
_field_name->to_string(), _name.to_cql_string()));
_field_name->to_string(), _name.to_string()));
}
if (to_update->size() == max_udt_fields) {
@@ -147,7 +147,7 @@ user_type alter_type_statement::add_or_alter::do_add(data_dictionary::database d
auto&& add_type = _field_type->prepare(db, keyspace()).get_type();
if (add_type->references_user_type(to_update->_keyspace, to_update->_name)) {
throw exceptions::invalid_request_exception(format("Cannot add new field {} of type {} to type {} as this would create a circular reference",
*_field_name, *_field_type, _name.to_cql_string()));
*_field_name, *_field_type, _name.to_string()));
}
new_types.push_back(std::move(add_type));
return user_type_impl::get_instance(to_update->_keyspace, to_update->_name, std::move(new_names), std::move(new_types), to_update->is_multi_cell());
@@ -157,7 +157,7 @@ user_type alter_type_statement::add_or_alter::do_alter(data_dictionary::database
{
auto idx = to_update->idx_of_field(_field_name->name());
if (!idx) {
throw exceptions::invalid_request_exception(format("Unknown field {} in type {}", _field_name->to_string(), _name.to_cql_string()));
throw exceptions::invalid_request_exception(format("Unknown field {} in type {}", _field_name->to_string(), _name.to_string()));
}
auto previous = to_update->field_types()[*idx];
@@ -194,7 +194,7 @@ user_type alter_type_statement::renames::make_updated_type(data_dictionary::data
auto&& from = rename.first;
auto idx = to_update->idx_of_field(from->name());
if (!idx) {
throw exceptions::invalid_request_exception(format("Unknown field {} in type {}", from->to_string(), _name.to_cql_string()));
throw exceptions::invalid_request_exception(format("Unknown field {} in type {}", from->to_string(), _name.to_string()));
}
new_names[*idx] = rename.second->name();
}

View File

@@ -65,8 +65,7 @@ void cql3::statements::authorization_statement::maybe_correct_resource(auth::res
// This is an "ALL FUNCTIONS IN KEYSPACE" resource.
return;
}
auto ks = qp.db().find_keyspace(*keyspace);
const auto& utm = ks.user_types();
const auto& utm = qp.db().find_keyspace(*keyspace).user_types();
auto function_name = *functions_view.function_name();
auto function_args = functions_view.function_args();
std::vector<data_type> parsed_types;

View File

@@ -90,22 +90,6 @@ create_aggregate_statement::prepare_schema_mutations(query_processor& qp, api::t
co_return std::make_pair(std::move(ret), std::move(m));
}
seastar::future<> create_aggregate_statement::check_access(query_processor &qp, const service::client_state &state) const {
co_await create_function_statement_base::check_access(qp, state);
auto&& ks = _name.has_keyspace() ? _name.keyspace : state.get_keyspace();
create_arg_types(qp);
std::vector<data_type> sfunc_args = _arg_types;
data_type stype = prepare_type(qp, *_stype);
sfunc_args.insert(sfunc_args.begin(), stype);
co_await state.has_function_access(qp.db(), ks, auth::encode_signature(_sfunc,sfunc_args), auth::permission::EXECUTE);
if (_rfunc) {
co_await state.has_function_access(qp.db(), ks, auth::encode_signature(*_rfunc,{stype, stype}), auth::permission::EXECUTE);
}
if (_ffunc) {
co_await state.has_function_access(qp.db(), ks, auth::encode_signature(*_ffunc,{stype}), auth::permission::EXECUTE);
}
}
create_aggregate_statement::create_aggregate_statement(functions::function_name name, std::vector<shared_ptr<cql3_type::raw>> arg_types,
sstring sfunc, shared_ptr<cql3_type::raw> stype, std::optional<sstring> rfunc, std::optional<sstring> ffunc, std::optional<expr::expression> ival, bool or_replace, bool if_not_exists)
: create_function_statement_base(std::move(name), std::move(arg_types), or_replace, if_not_exists)

View File

@@ -26,7 +26,6 @@ namespace statements {
class create_aggregate_statement final : public create_function_statement_base {
virtual std::unique_ptr<prepared_statement> prepare(data_dictionary::database db, cql_stats& stats) override;
future<std::pair<::shared_ptr<cql_transport::event::schema_change>, std::vector<mutation>>> prepare_schema_mutations(query_processor& qp, api::timestamp_type) const override;
virtual future<> check_access(query_processor& qp, const service::client_state& state) const override;
virtual seastar::future<shared_ptr<db::functions::function>> create(query_processor& qp, db::functions::function* old) const override;

View File

@@ -134,7 +134,7 @@ future<std::pair<::shared_ptr<cql_transport::event::schema_change>, std::vector<
_name.get_string_type_name());
} else {
if (!_if_not_exists) {
co_await coroutine::return_exception(exceptions::invalid_request_exception(format("A user type of name {} already exists", _name.to_cql_string())));
co_await coroutine::return_exception(exceptions::invalid_request_exception(format("A user type of name {} already exists", _name.to_string())));
}
}
} catch (data_dictionary::no_such_keyspace& e) {

View File

@@ -52,7 +52,7 @@ drop_keyspace_statement::prepare_schema_mutations(query_processor& qp, api::time
::shared_ptr<cql_transport::event::schema_change> ret;
try {
m = co_await qp.get_migration_manager().prepare_keyspace_drop_announcement(_keyspace, ts);
m = qp.get_migration_manager().prepare_keyspace_drop_announcement(_keyspace, ts);
using namespace cql_transport;
ret = ::make_shared<event::schema_change>(

View File

@@ -56,7 +56,7 @@ void drop_type_statement::validate_while_executing(query_processor& qp) const {
if (_if_exists) {
return;
} else {
throw exceptions::invalid_request_exception(format("No user type named {} exists.", _name.to_cql_string()));
throw exceptions::invalid_request_exception(format("No user type named {} exists.", _name.to_string()));
}
}

View File

@@ -393,7 +393,7 @@ modification_statement::process_where_clause(data_dictionary::database db, expr:
_has_regular_column_conditions = true;
}
}
if (_restrictions->has_token_restrictions()) {
if (has_token(_restrictions->get_partition_key_restrictions())) {
throw exceptions::invalid_request_exception(format("The token function cannot be used in WHERE clauses for UPDATE and DELETE statements: {}",
to_string(_restrictions->get_partition_key_restrictions())));
}

View File

@@ -11,7 +11,6 @@
#include <seastar/core/thread.hh>
#include "auth/service.hh"
#include "db/system_keyspace.hh"
#include "permission_altering_statement.hh"
#include "cql3/functions/functions.hh"
#include "cql3/functions/user_aggregate.hh"
@@ -50,10 +49,15 @@ future<> cql3::statements::permission_altering_statement::check_access(query_pro
return state.ensure_exists(_resource).then([this, &state] {
if (_resource.kind() == auth::resource_kind::functions) {
// Even if the resource exists, it may be a builtin function or all builtin functions, in which case we disallow altering permissions on it.
// Even if the function exists, it may be a builtin function, in which case we disallow altering permissions on it.
auth::functions_resource_view v(_resource);
if (v.keyspace() && *v.keyspace() == db::system_keyspace::NAME) {
return make_exception_future<>(exceptions::invalid_request_exception("Altering permissions on builtin functions is not supported"));
if (v.function_signature()) {
// If the resource has a signature, it is a specific funciton and not "all functions"
auto [name, function_args] = auth::decode_signature(*v.function_signature());
auto fun = cql3::functions::functions::find(db::functions::function_name{sstring(*v.keyspace()), name}, function_args);
if (fun->is_native()) {
return make_exception_future<>(exceptions::invalid_request_exception("Altering permissions on builtin functions is not supported"));
}
}
}
// check that the user has AUTHORIZE permission on the resource or its parents, otherwise reject

View File

@@ -75,7 +75,7 @@ public:
template<typename T>
using compare_fn = std::function<bool(const T&, const T&)>;
using result_row_type = std::vector<managed_bytes_opt>;
using result_row_type = std::vector<bytes_opt>;
using ordering_comparator_type = compare_fn<result_row_type>;
private:
using prepared_orderings_type = std::vector<std::pair<const column_definition*, ordering>>;

View File

@@ -46,7 +46,6 @@
#include "utils/result_combinators.hh"
#include "utils/result_loop.hh"
#include "service/forward_service.hh"
#include "replica/database.hh"
template<typename T = void>
using coordinator_result = cql3::statements::select_statement::coordinator_result<T>;
@@ -561,9 +560,7 @@ indexed_table_select_statement::do_execute_base_query(
auto cmd = prepare_command_for_base_query(qp, options, state, now, bool(paging_state));
auto timeout = db::timeout_clock::now() + get_timeout(state.get_client_state(), options);
uint32_t queried_ranges_count = partition_ranges.size();
auto&& table = qp.proxy().local_db().find_column_family(_schema);
auto erm = table.get_effective_replication_map();
query_ranges_to_vnodes_generator ranges_to_vnodes(erm->make_splitter(), _schema, std::move(partition_ranges));
query_ranges_to_vnodes_generator ranges_to_vnodes(qp.proxy().get_token_metadata_ptr(), _schema, std::move(partition_ranges));
struct base_query_state {
query::result_merger merger;
@@ -876,7 +873,7 @@ primary_key_select_statement::primary_key_select_statement(schema_ptr schema, ui
if (_ks_sel == ks_selector::NONSYSTEM) {
if (_restrictions->need_filtering() ||
_restrictions->partition_key_restrictions_is_empty() ||
(_restrictions->has_token_restrictions() &&
(has_token(_restrictions->get_partition_key_restrictions()) &&
!find(_restrictions->get_partition_key_restrictions(), expr::oper_t::EQ))) {
_range_scan = true;
if (!_parameters->bypass_cache())
@@ -900,8 +897,7 @@ indexed_table_select_statement::prepare(data_dictionary::database db,
cql_stats &stats,
std::unique_ptr<attributes> attrs)
{
auto cf = db.find_column_family(schema);
auto& sim = cf.get_index_manager();
auto& sim = db.find_column_family(schema).get_index_manager();
auto [index_opt, used_index_restrictions] = restrictions->find_idx(sim);
if (!index_opt) {
throw std::runtime_error("No index found.");
@@ -1212,7 +1208,7 @@ query::partition_slice indexed_table_select_statement::get_partition_slice_for_g
partition_slice_builder partition_slice_builder{*_view_schema};
if (!_restrictions->has_partition_key_unrestricted_components()) {
bool pk_restrictions_is_single = !_restrictions->has_token_restrictions();
bool pk_restrictions_is_single = !has_token(_restrictions->get_partition_key_restrictions());
// Only EQ restrictions on base partition key can be used in an index view query
if (pk_restrictions_is_single && _restrictions->partition_key_restrictions_is_all_eq()) {
partition_slice_builder.with_ranges(
@@ -2028,7 +2024,7 @@ static bool needs_allow_filtering_anyway(
const auto& pk_restrictions = restrictions.get_partition_key_restrictions();
// Even if no filtering happens on the coordinator, we still warn about poor performance when partition
// slice is defined but in potentially unlimited number of partitions (see #7608).
if ((expr::is_empty_restriction(pk_restrictions) || restrictions.has_token_restrictions()) // Potentially unlimited partitions.
if ((expr::is_empty_restriction(pk_restrictions) || has_token(pk_restrictions)) // Potentially unlimited partitions.
&& !expr::is_empty_restriction(ck_restrictions) // Slice defined.
&& !restrictions.uses_secondary_indexing()) { // Base-table is used. (Index-table use always limits partitions.)
if (strict_allow_filtering == flag_t::WARN) {

View File

@@ -56,7 +56,13 @@ public:
return size_t(_type);
}
bool operator==(const statement_type&) const = default;
bool operator==(const statement_type& other) const {
return _type == other._type;
}
bool operator!=(const statement_type& other) const {
return !(_type == other._type);
}
friend std::ostream &operator<<(std::ostream &os, const statement_type& t) {
switch (t._type) {

View File

@@ -119,7 +119,7 @@ strongly_consistent_select_statement::execute_without_checking_exception_message
});
if (query_result->value) {
result_set->add_row({ managed_bytes_opt(query_result->value) });
result_set->add_row({ query_result->value });
}
co_return ::make_shared<cql_transport::messages::result_message::rows>(cql3::result{std::move(result_set)});

View File

@@ -85,7 +85,7 @@ private:
uint64_t _query_cnt[(size_t)source_selector::SIZE]
[(size_t)ks_selector::SIZE]
[(size_t)cond_selector::SIZE]
[statements::statement_type::MAX_VALUE + 1] = {};
[statements::statement_type::MAX_VALUE + 1] = {0ul};
};
}

View File

@@ -30,17 +30,17 @@ size_t cql3::untyped_result_set_row::index(const std::string_view& name) const {
bool cql3::untyped_result_set_row::has(std::string_view name) const {
auto i = index(name);
if (i < _data.size()) {
return _data.at(i).has_value();
return !std::holds_alternative<std::monostate>(_data.at(i));
}
return false;
}
cql3::untyped_result_set_row::view_type cql3::untyped_result_set_row::get_view(std::string_view name) const {
auto& data = _data.at(index(name));
if (!data) {
throw std::bad_variant_access();
}
return *data;
return std::visit(make_visitor(
[](std::monostate) -> view_type { throw std::bad_variant_access(); },
[](const view_type& v) -> view_type { return v; },
[](const bytes& b) -> view_type { return view_type(b); }
), _data.at(index(name)));
}
const std::vector<lw_shared_ptr<cql3::column_specification>>& cql3::untyped_result_set_row::get_columns() const {
@@ -74,12 +74,12 @@ struct cql3::untyped_result_set::visitor {
void start_row() {
tmp.reserve(index.size());
}
void accept_value(managed_bytes_view_opt&& v) {
void accept_value(std::optional<query::result_bytes_view>&& v) {
if (v) {
tmp.emplace_back(*v);
tmp.emplace_back(std::move(*v));
} else {
tmp.emplace_back(std::nullopt);
}
tmp.emplace_back(std::monostate{});
}
}
// somewhat weird dispatch, but when visiting directly via
// result_generator, pk:s will be temporary - and sent

View File

@@ -41,12 +41,13 @@ class metadata;
class untyped_result_set_row {
public:
using view_type = managed_bytes_view;
using view_type = query::result_bytes_view;
using opt_view_type = std::optional<view_type>;
using view_holder = std::variant<std::monostate, view_type, bytes>;
private:
friend class untyped_result_set;
using index_map = std::unordered_map<std::string_view, size_t>;
using data_views = std::vector<managed_bytes_opt>;
using data_views = std::vector<view_holder>;
const index_map& _name_to_index;
const cql3::metadata& _metadata;
@@ -61,7 +62,7 @@ public:
bool has(std::string_view) const;
view_type get_view(std::string_view name) const;
bytes get_blob(std::string_view name) const {
return to_bytes(get_view(name));
return get_view(name).linearize();
}
managed_bytes get_blob_fragmented(std::string_view name) const {
return managed_bytes(get_view(name));
@@ -149,8 +150,6 @@ public:
class result_set;
/// A tabular result. Unlike result_set, untyped_result_set is optimized for safety
/// and convenience, not performance.
class untyped_result_set {
public:
using row = untyped_result_set_row;

View File

@@ -39,8 +39,8 @@ sstring ut_name::get_string_type_name() const
return _ut_name->to_string();
}
sstring ut_name::to_cql_string() const {
return (has_keyspace() ? (_ks_name.value() + ".") : "") + _ut_name->to_cql_string();
sstring ut_name::to_string() const {
return (has_keyspace() ? (_ks_name.value() + ".") : "") + _ut_name->to_string();
}
}

Some files were not shown because too many files have changed in this diff Show More