Files
scylladb/cql3/update_parameters.cc
Nadav Har'El c87d6407ed cql3: Replace SCYLLA_ASSERT by throwing_assert
In this patch we replace every single use of SCYLLA_ASSERT() in the cql3/
directory by throwing_assert().

The problem with SCYLLA_ASSERT() is that when it fails, it crashes Scylla.
This is almost always a bad idea (see #7871 discussing why), but it's even
riskier in front-end code like cql3/: In front-end code, there is a risk
that due to a bug in our code, a specific user request can cause Scylla
to crash. A malicious user can send this query to all nodes and crash
the entire cluster. When the user is not malicious, it causes a small
problem (a failing request) to become a much worse crash - and worse,
the user has no idea which request is causing this crash and the crash
will repeat if the same request is tried again.

All of this is solved by using the new throwing_assert(), which is the
same as SCYLLA_ASSERT() but throws an exception (using on_internal_error())
instead of crashing. The exception will prevent the code path with the
invalid assumption from continuing, but will result in only the current
user request being aborted, with a clear error message reporting the
internal server error due to an assertion failure.

I reviewed all the changes that I did in this patch to check that (to the
best of my understanding) none of the assertions in cql3/ involve the
sort of serious corruption that might require crashing the Scylla node
entirely.

throwing_assert() also improves logging of assertion failures compared
to the original SCYLLA_ASSERT() - SCYLLA_ASSERT() printed a message to
stderr which in many installations is lost, whereas throwing_assert()
uses Scylla's standard logger, and also includes a backtrace in the
log message.

Fixes #13970 (Exorcise assertions from CQL code paths)
Refs #7871 (Exorcise assertions from Scylla)

Signed-off-by: Nadav Har'El <nyh@scylladb.com>
2026-03-11 09:41:20 +02:00

160 lines
5.2 KiB
C++

/*
* Copyright (C) 2015-present ScyllaDB
*
* Modified by ScyllaDB
*/
/*
* SPDX-License-Identifier: (LicenseRef-ScyllaDB-Source-Available-1.0 and Apache-2.0)
*/
#include "utils/assert.hh"
#include "cql3/update_parameters.hh"
#include "cql3/selection/selection.hh"
#include "cql3/expr/expression.hh"
#include "cql3/expr/evaluate.hh"
#include "cql3/expr/expr-utils.hh"
#include "query/query-result-reader.hh"
#include "types/map.hh"
namespace cql3 {
std::optional<std::vector<std::pair<data_value, data_value>>>
update_parameters::get_prefetched_list(const partition_key& pkey, const clustering_key& ckey, const column_definition& column) const {
auto row = _prefetched.find_row(pkey, column.is_static() ? clustering_key::make_empty() : ckey);
if (row == nullptr) {
return std::nullopt;
}
auto pkey_bytes = pkey.explode();
auto ckey_bytes = ckey.explode();
auto val = expr::extract_column_value(&column, expr::evaluation_inputs{
.partition_key = pkey_bytes,
.clustering_key = ckey_bytes,
.static_and_regular_columns = row->cells,
.selection = _prefetched.selection.get(),
});
if (!val) {
return std::nullopt;
}
auto type = column.type;
// We use collections_as_maps flag, so set/list type is map, reconstruct the
// data type used for serialization.
if (type->is_listlike() && type->is_multi_cell()) {
auto ctype = static_pointer_cast<const collection_type_impl>(type);
type = map_type_impl::get_instance(ctype->name_comparator(), ctype->value_comparator(), true);
}
// Ensured by collections_as_maps flag in read_command flags
throwing_assert(type->is_map());
auto cell = type->deserialize(managed_bytes_view(*val));
const map_type_impl& map_type = static_cast<const map_type_impl&>(*cell.type());
return map_type.from_value(cell);
}
update_parameters::prefetch_data::prefetch_data(schema_ptr schema)
: rows(key_less{*schema})
, schema(schema)
{ }
const update_parameters::prefetch_data::row*
update_parameters::prefetch_data::find_row(const partition_key& pkey, const clustering_key& ckey) const {
const auto it = rows.find({pkey, ckey});
return it == rows.end() ? nullptr : &it->second;
}
// Implements ResultVisitor concept from query.hh
class prefetch_data_builder {
update_parameters::prefetch_data& _data;
schema_ptr _schema;
std::optional<partition_key> _pkey;
public:
prefetch_data_builder(schema_ptr s, update_parameters::prefetch_data& data)
: _data(data)
, _schema(std::move(s))
{ }
void update_has_static(update_parameters::prefetch_data::row& cells) {
cells.has_static = false;
size_t idx = 0;
for (auto* cdef : _data.selection->get_columns()) {
if (cdef->is_regular()) {
// no more static columns
break;
}
if (cdef->is_static() && cells.cells[idx]) {
cells.has_static = true;
break;
}
++idx;
}
}
void accept_new_partition(const partition_key& key, uint64_t row_count) {
_pkey = key;
}
void accept_new_partition(uint64_t row_count) {
throwing_assert(0);
}
void accept_new_row(const clustering_key& key, const query::result_row_view& static_row,
const query::result_row_view& row) {
update_parameters::prefetch_data::row cells;
cells.cells = expr::get_non_pk_values(*_data.selection, static_row, &row);
update_has_static(cells);
_data.rows.emplace(std::make_pair(*_pkey, key), std::move(cells));
}
void accept_new_row(const query::result_row_view& static_row, const query::result_row_view& row) {
throwing_assert(0);
}
void accept_partition_end(const query::result_row_view& static_row) {
if (!_schema->clustering_key_size() || !_schema->has_static_columns()) {
// Do not add an (empty) static row if there are no
// clustering key columns or not static cells, such
// row will have no non-null cells in it, so will
// be useless.
return;
}
// When no clustering row matches WHERE condition of
// UPSERT-like operation (INSERT, UPDATE)
// the static row will be used to materialize the initial
// clustering row.
update_parameters::prefetch_data::row cells;
cells.cells = expr::get_non_pk_values(*_data.selection, static_row, nullptr);
update_has_static(cells);
// We end up here only if the table has a clustering key,
// so no other row added for this partition thus has an
// empty ckey.
_data.rows.emplace(std::make_pair(*_pkey, clustering_key_prefix::make_empty()), std::move(cells));
}
};
update_parameters::prefetch_data update_parameters::build_prefetch_data(schema_ptr schema, const query::result& query_result,
const query::partition_slice& slice) {
update_parameters::prefetch_data rows(schema);
rows.selection = selection::selection_from_partition_slice(schema, slice);
query::result_view::consume(query_result, slice, prefetch_data_builder(schema, rows));
return rows;
}
} // end of namespace cql3