Files
scylladb/test/lib/cql_assertions.hh
Marcin Maliszkiewicz 81685b0d06 Merge 'db/batchlog_manager: re-add v1 support for mixed clusters' from Botond Dénes
3f7ee3ce5d introduced system.batchlog_v2, with a schema designed to speed up batchlog replays and make post-replay cleanups much more effective.
It did not introduce a cluster feature for the new table, because it is node local table, so the cluster can switch to the new table gradually, one node at a time.
However, https://github.com/scylladb/scylladb/issues/27886 showed that the switching causes timeouts during upgrades, in mixed clusters. Furthermore, switching to the new table unconditionally  on upgrades nodes, means that on rollback, the batches saved into the v2 table are lost.
This PR introduces re-introduces v1 (`system.batchlog`) support and guards the use of the v2 table with a cluster feature, so mixed clusters keep using v1 and thus be rollback-compatible.
The re-introduced v1 support doesn't support post-replay cleanups for simplicity. The cleanup in v1 was never particularly effective anyway and we ended up disabling it for heavy batchlog users, so I don't think the lack of support for cleanup is a problem.

Fixes: https://github.com/scylladb/scylladb/issues/27886

Needs backport to 2026.1, to fix upgrades for clusters using batches

Closes scylladb/scylladb#28736

* github.com:scylladb/scylladb:
  test/boost/batchlog_manager_test: add tests for v1 batchlog
  test/boost/batchlog_manager_test: make prepare_batches() work with both v1 and v2
  test/boost/batchlog_manager_test: fix indentation
  test/boost/batchlog_manager_test: extract prepare_batches() method
  test/lib/cql_assertions: is_rows(): add dump parameter
  tools/scylla-sstable: extract query result printers
  tools/scylla-sstable: add std::ostream& arg to query result printers
  repair/row_level: repair_flush_hints_batchlog_handler(): add all_replayed to finish log
  db/batchlog_manager: re-add v1 support
  db/batchlog_manager: return all_replayed from process_batch()
  db/batchlog_manager: process_bath() fix indentation
  db/batchlog_manager: make batch() a standalone function
  db/batchlog_manager: make structs stats public
  db/batchlog_manager: allocate limiter on the stack
  db/batchlog_manager: add feature_service dependency
  gms/feature_service: add batchlog_v2 feature

(cherry picked from commit a83ee6cf66)

Closes scylladb/scylladb#28853
2026-03-04 08:28:39 +02:00

173 lines
6.6 KiB
C++

/*
* Copyright (C) 2015-present ScyllaDB
*/
/*
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
*/
#pragma once
#include "utils/assert.hh"
#include "utils/managed_bytes.hh"
#include "test/lib/cql_test_env.hh"
#include "transport/messages/result_message_base.hh"
#include "bytes.hh"
#include <source_location>
#include <seastar/core/shared_ptr.hh>
#include <seastar/core/future.hh>
class columns_assertions {
const cql3::metadata& _metadata;
const std::vector<managed_bytes_opt>& _columns;
std::source_location _loc;
columns_assertions& do_with_raw_column(const char* name, std::function<void(data_type, managed_bytes_view)> func);
void fail(const sstring& msg);
public:
columns_assertions(const cql3::metadata& metadata, const std::vector<managed_bytes_opt>& columns, std::source_location loc)
: _metadata(metadata), _columns(columns), _loc(loc)
{ }
columns_assertions& with_raw_column(const char* name, std::function<bool(managed_bytes_view)> predicate);
columns_assertions& with_raw_column(const char* name, managed_bytes_view value);
template <typename T>
columns_assertions& with_typed_column(const char* name, std::function<bool(const T* value)> predicate) {
return do_with_raw_column(name, [this, name, predicate] (data_type type, managed_bytes_view value) {
if (type != data_type_for<T>()) {
fail(seastar::format("Column {} is not of type {}, but of type {}", name, data_type_for<T>()->name(), type->name()));
}
std::optional<T> t_opt;
if (!value.empty()) {
t_opt.emplace(value_cast<T>(type->deserialize(value)));
}
if (!predicate(t_opt.has_value() ? &t_opt.value() : nullptr)) {
fail(seastar::format("Column {} failed predicate check: value = {}", name, value));
}
});
}
template <typename T>
columns_assertions& with_typed_column(const char* name, std::function<bool(const T& value)> predicate) {
return with_typed_column<T>(name, [this, name, predicate] (const T* value) {
if (!value) {
fail(seastar::format("Column {} is null", name));
}
return predicate(*value);
});
}
template <typename T>
columns_assertions& with_typed_column(const char* name, const T& value) {
return with_typed_column<T>(name, [this, name, &value] (const T& cell_value) {
if (cell_value != value) {
fail(seastar::format("Expected column {} to have value {}, but got {}", name, value, cell_value));
}
return true;
});
}
};
namespace tests {
using dump_to_logs = bool_class<struct dump_to_logs_tag>;
} // namespace tests
class rows_assertions {
shared_ptr<cql_transport::messages::result_message::rows> _rows;
std::source_location _loc;
public:
rows_assertions(shared_ptr<cql_transport::messages::result_message::rows> rows, tests::dump_to_logs dump, std::source_location loc);
rows_assertions with_size(size_t size);
rows_assertions with_size(std::function<bool(size_t)> predicate);
rows_assertions is_empty();
rows_assertions is_not_empty();
rows_assertions with_column_types(std::initializer_list<data_type> column_types);
rows_assertions with_row(std::initializer_list<bytes_opt> values);
// Verifies that the result has the following rows and only that rows, in that order.
rows_assertions with_rows(std::vector<std::vector<bytes_opt>> rows);
// Verifies that the result has the following rows and only those rows.
rows_assertions with_rows_ignore_order(std::vector<std::vector<bytes_opt>> rows);
rows_assertions with_serialized_columns_count(size_t columns_count);
columns_assertions with_columns_of_row(size_t row_index);
rows_assertions& assert_for_columns_of_each_row(std::function<void(columns_assertions&)> func);
rows_assertions is_null();
rows_assertions is_not_null();
};
class result_msg_assertions {
shared_ptr<cql_transport::messages::result_message> _msg;
std::source_location _loc;
public:
result_msg_assertions(shared_ptr<cql_transport::messages::result_message> msg, std::source_location loc);
// Pass dump_to_logs::yes, to dump the content of the result to the log.
// The results are logged with testlog.debug().
rows_assertions is_rows(tests::dump_to_logs dump = tests::dump_to_logs::no);
};
result_msg_assertions assert_that(shared_ptr<cql_transport::messages::result_message> msg, std::source_location loc = std::source_location::current());
template<typename T>
void assert_that_failed(future<T>& f)
{
try {
f.get();
SCYLLA_ASSERT(f.failed());
}
catch (...) {
}
}
template<typename T>
void assert_that_failed(future<T>&& f)
{
try {
f.get();
SCYLLA_ASSERT(f.failed());
}
catch (...) {
}
}
/// Invokes env.execute_cql(query), awaits its result, and returns it. If an exception is thrown,
/// invokes BOOST_FAIL with useful diagnostics.
///
/// \note Should be called from a seastar::thread context, as it awaits the CQL result.
shared_ptr<cql_transport::messages::result_message> cquery_nofail(
cql_test_env& env,
std::string_view query,
std::unique_ptr<cql3::query_options>&& qo = nullptr,
const std::source_location& loc = std::source_location::current());
/// Asserts that cquery_nofail(e, qstr) contains expected rows, in any order.
void require_rows(cql_test_env& e,
std::string_view qstr,
const std::vector<std::vector<bytes_opt>>& expected,
const std::source_location& loc = std::source_location::current());
/// Like require_rows, but wraps assertions in \c eventually.
void eventually_require_rows(
cql_test_env& e, std::string_view qstr, const std::vector<std::vector<bytes_opt>>& expected,
const std::source_location& loc = std::source_location::current());
/// Asserts that e.execute_prepared(id, values) contains expected rows, in any order.
void require_rows(cql_test_env& e,
cql3::prepared_cache_key_type id,
const std::vector<cql3::raw_value>& values,
const std::vector<std::vector<bytes_opt>>& expected,
const std::source_location& loc = std::source_location::current());
/// Asserts that a cell at the given table.partition.row.column position contains expected data
future<> require_column_has_value(cql_test_env&, const sstring& table_name,
std::vector<data_value> pk, std::vector<data_value> ck, const sstring& column_name, data_value expected);