these unused includes were identified by clangd. see https://clangd.llvm.org/guides/include-cleaner#unused-include-warning for more details on the "Unused include" warning. Signed-off-by: Kefu Chai <kefu.chai@scylladb.com> Closes scylladb/scylladb#16791
327 lines
13 KiB
C++
327 lines
13 KiB
C++
/*
|
|
* Copyright (C) 2015-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
#include "lists.hh"
|
|
#include "update_parameters.hh"
|
|
#include "column_identifier.hh"
|
|
#include "cql3/expr/evaluate.hh"
|
|
#include "cql3/expr/expr-utils.hh"
|
|
#include <boost/iterator/transform_iterator.hpp>
|
|
#include "types/list.hh"
|
|
#include "utils/UUID_gen.hh"
|
|
#include "mutation/mutation.hh"
|
|
|
|
namespace cql3 {
|
|
|
|
lw_shared_ptr<column_specification>
|
|
lists::index_spec_of(const column_specification& column) {
|
|
return make_lw_shared<column_specification>(column.ks_name, column.cf_name,
|
|
::make_shared<column_identifier>(format("idx({})", *column.name), true), int32_type);
|
|
}
|
|
|
|
lw_shared_ptr<column_specification>
|
|
lists::uuid_index_spec_of(const column_specification& column) {
|
|
return make_lw_shared<column_specification>(column.ks_name, column.cf_name,
|
|
::make_shared<column_identifier>(format("uuid_idx({})", *column.name), true), uuid_type);
|
|
}
|
|
|
|
void
|
|
lists::setter::execute(mutation& m, const clustering_key_prefix& prefix, const update_parameters& params) {
|
|
auto value = expr::evaluate(*_e, params._options);
|
|
execute(m, prefix, params, column, std::move(value));
|
|
}
|
|
|
|
void
|
|
lists::setter::execute(mutation& m, const clustering_key_prefix& prefix, const update_parameters& params, const column_definition& column, const cql3::raw_value& value) {
|
|
if (column.type->is_multi_cell()) {
|
|
// Delete all cells first, then append new ones
|
|
collection_mutation_view_description mut;
|
|
mut.tomb = params.make_tombstone_just_before();
|
|
|
|
m.set_cell(prefix, column, mut.serialize(*column.type));
|
|
}
|
|
do_append(value, m, prefix, column, params);
|
|
}
|
|
|
|
bool
|
|
lists::setter_by_index::requires_read() const {
|
|
return true;
|
|
}
|
|
|
|
void
|
|
lists::setter_by_index::fill_prepare_context(prepare_context& ctx) {
|
|
operation::fill_prepare_context(ctx);
|
|
expr::fill_prepare_context(_idx, ctx);
|
|
}
|
|
|
|
void
|
|
lists::setter_by_index::execute(mutation& m, const clustering_key_prefix& prefix, const update_parameters& params) {
|
|
// we should not get here for frozen lists
|
|
assert(column.type->is_multi_cell()); // "Attempted to set an individual element on a frozen list";
|
|
|
|
auto index = expr::evaluate(_idx, params._options);
|
|
if (index.is_null()) {
|
|
throw exceptions::invalid_request_exception("Invalid null value for list index");
|
|
}
|
|
auto value = expr::evaluate(*_e, params._options);
|
|
|
|
auto idx = index.view().deserialize<int32_t>(*int32_type);
|
|
auto&& existing_list_opt = params.get_prefetched_list(m.key(), prefix, column);
|
|
if (!existing_list_opt) {
|
|
throw exceptions::invalid_request_exception("Attempted to set an element on a list which is null");
|
|
}
|
|
auto&& existing_list = *existing_list_opt;
|
|
// we verified that index is an int32_type
|
|
if (idx < 0 || size_t(idx) >= existing_list.size()) {
|
|
throw exceptions::invalid_request_exception(format("List index {:d} out of bound, list has size {:d}",
|
|
idx, existing_list.size()));
|
|
}
|
|
|
|
auto ltype = static_cast<const list_type_impl*>(column.type.get());
|
|
const data_value& eidx_dv = existing_list[idx].first;
|
|
bytes eidx = eidx_dv.type()->decompose(eidx_dv);
|
|
collection_mutation_description mut;
|
|
mut.cells.reserve(1);
|
|
if (value.is_null()) {
|
|
mut.cells.emplace_back(std::move(eidx), params.make_dead_cell());
|
|
} else {
|
|
mut.cells.emplace_back(std::move(eidx),
|
|
params.make_cell(*ltype->value_comparator(), value.view(), atomic_cell::collection_member::yes));
|
|
}
|
|
|
|
m.set_cell(prefix, column, mut.serialize(*ltype));
|
|
}
|
|
|
|
bool
|
|
lists::setter_by_uuid::requires_read() const {
|
|
return false;
|
|
}
|
|
|
|
void
|
|
lists::setter_by_uuid::execute(mutation& m, const clustering_key_prefix& prefix, const update_parameters& params) {
|
|
// we should not get here for frozen lists
|
|
assert(column.type->is_multi_cell()); // "Attempted to set an individual element on a frozen list";
|
|
|
|
auto index = expr::evaluate(_idx, params._options);
|
|
auto value = expr::evaluate(*_e, params._options);
|
|
|
|
if (index.is_null()) {
|
|
throw exceptions::invalid_request_exception("Invalid null value for list index");
|
|
}
|
|
|
|
auto ltype = static_cast<const list_type_impl*>(column.type.get());
|
|
|
|
collection_mutation_description mut;
|
|
mut.cells.reserve(1);
|
|
|
|
if (value.is_null()) {
|
|
mut.cells.emplace_back(std::move(index).to_bytes(), params.make_dead_cell());
|
|
} else {
|
|
mut.cells.emplace_back(
|
|
std::move(index).to_bytes(),
|
|
params.make_cell(*ltype->value_comparator(), value.view(), atomic_cell::collection_member::yes));
|
|
}
|
|
|
|
m.set_cell(prefix, column, mut.serialize(*ltype));
|
|
}
|
|
|
|
void
|
|
lists::appender::execute(mutation& m, const clustering_key_prefix& prefix, const update_parameters& params) {
|
|
const cql3::raw_value value = expr::evaluate(*_e, params._options);
|
|
assert(column.type->is_multi_cell()); // "Attempted to append to a frozen list";
|
|
do_append(value, m, prefix, column, params);
|
|
}
|
|
|
|
void
|
|
lists::do_append(const cql3::raw_value& list_value,
|
|
mutation& m,
|
|
const clustering_key_prefix& prefix,
|
|
const column_definition& column,
|
|
const update_parameters& params) {
|
|
if (column.type->is_multi_cell()) {
|
|
// If we append null, do nothing. Note that for Setter, we've
|
|
// already removed the previous value so we're good here too
|
|
if (list_value.is_null()) {
|
|
return;
|
|
}
|
|
|
|
auto ltype = static_cast<const list_type_impl*>(column.type.get());
|
|
|
|
auto&& to_add = expr::get_list_elements(list_value);
|
|
collection_mutation_description appended;
|
|
appended.cells.reserve(to_add.size());
|
|
for (auto&& e : to_add) {
|
|
try {
|
|
auto uuid1 = utils::UUID_gen::get_time_UUID_bytes_from_micros_and_submicros(
|
|
std::chrono::microseconds{params.timestamp()},
|
|
params._options.next_list_append_seq());
|
|
auto uuid = bytes(reinterpret_cast<const int8_t*>(uuid1.data()), uuid1.size());
|
|
if (!e) {
|
|
throw exceptions::invalid_request_exception("Invalid NULL element in list");
|
|
}
|
|
// FIXME: can e be empty?
|
|
appended.cells.emplace_back(
|
|
std::move(uuid),
|
|
params.make_cell(*ltype->value_comparator(), *e, atomic_cell::collection_member::yes));
|
|
} catch (utils::timeuuid_submicro_out_of_range&) {
|
|
throw exceptions::invalid_request_exception("Too many list values per single CQL statement or batch");
|
|
}
|
|
}
|
|
m.set_cell(prefix, column, appended.serialize(*ltype));
|
|
} else {
|
|
auto ltype = static_cast<const list_type_impl*>(column.type.get());
|
|
// for frozen lists, we're overwriting the whole cell value
|
|
if (list_value.is_null()) {
|
|
m.set_cell(prefix, column, params.make_dead_cell());
|
|
} else {
|
|
list_value.view().with_value([&] (const FragmentedView auto& v) {
|
|
ltype->validate_for_storage(v);
|
|
});
|
|
m.set_cell(prefix, column, params.make_cell(*column.type, list_value.view()));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
lists::prepender::execute(mutation& m, const clustering_key_prefix& prefix, const update_parameters& params) {
|
|
assert(column.type->is_multi_cell()); // "Attempted to prepend to a frozen list";
|
|
cql3::raw_value lvalue = expr::evaluate(*_e, params._options);
|
|
if (lvalue.is_null()) {
|
|
return;
|
|
}
|
|
|
|
// For prepend we need to be able to generate a unique but decreasing
|
|
// timeuuid. We achieve that by by using a time in the past which
|
|
// is 2x the distance between the original timestamp (it
|
|
// would be the current timestamp, user supplied timestamp, or
|
|
// unique monotonic LWT timestsamp, whatever is in query
|
|
// options) and a reference time of Jan 1 2010 00:00:00.
|
|
// E.g. if query timestamp is Jan 1 2020 00:00:00, the prepend
|
|
// timestamp will be Jan 1, 2000, 00:00:00.
|
|
|
|
// 2010-01-01T00:00:00+00:00 in api::timestamp_time format (microseconds)
|
|
static constexpr int64_t REFERENCE_TIME_MICROS = 1262304000L * 1000 * 1000;
|
|
|
|
int64_t micros = params.timestamp();
|
|
if (micros > REFERENCE_TIME_MICROS) {
|
|
micros = REFERENCE_TIME_MICROS - (micros - REFERENCE_TIME_MICROS);
|
|
} else {
|
|
// Scylla, unlike Cassandra, respects user-supplied timestamps
|
|
// in prepend, but there is nothing useful it can do with
|
|
// a timestamp less than Jan 1, 2010, 00:00:00.
|
|
throw exceptions::invalid_request_exception("List prepend custom timestamp must be greater than Jan 1 2010 00:00:00");
|
|
}
|
|
|
|
collection_mutation_description mut;
|
|
utils::chunked_vector<managed_bytes_opt> list_elements = expr::get_list_elements(lvalue);
|
|
mut.cells.reserve(list_elements.size());
|
|
|
|
auto ltype = static_cast<const list_type_impl*>(column.type.get());
|
|
int clockseq = params._options.next_list_prepend_seq(list_elements.size(), utils::UUID_gen::SUBMICRO_LIMIT);
|
|
for (auto&& v : list_elements) {
|
|
try {
|
|
auto uuid = utils::UUID_gen::get_time_UUID_bytes_from_micros_and_submicros(std::chrono::microseconds{micros}, clockseq++);
|
|
if (!v) {
|
|
throw exceptions::invalid_request_exception("Invalid NULL element in list");
|
|
}
|
|
mut.cells.emplace_back(bytes(uuid.data(), uuid.size()), params.make_cell(*ltype->value_comparator(), *v, atomic_cell::collection_member::yes));
|
|
} catch (utils::timeuuid_submicro_out_of_range&) {
|
|
throw exceptions::invalid_request_exception("Too many list values per single CQL statement or batch");
|
|
}
|
|
}
|
|
m.set_cell(prefix, column, mut.serialize(*ltype));
|
|
}
|
|
|
|
bool
|
|
lists::discarder::requires_read() const {
|
|
return true;
|
|
}
|
|
|
|
void
|
|
lists::discarder::execute(mutation& m, const clustering_key_prefix& prefix, const update_parameters& params) {
|
|
assert(column.type->is_multi_cell()); // "Attempted to delete from a frozen list";
|
|
|
|
auto&& existing_list = params.get_prefetched_list(m.key(), prefix, column);
|
|
// We want to call bind before possibly returning to reject queries where the value provided is not a list.
|
|
cql3::raw_value lvalue = expr::evaluate(*_e, params._options);
|
|
|
|
if (!existing_list) {
|
|
return;
|
|
}
|
|
|
|
auto&& elist = *existing_list;
|
|
|
|
if (elist.empty()) {
|
|
return;
|
|
}
|
|
|
|
if (lvalue.is_null()) {
|
|
return;
|
|
}
|
|
|
|
auto ltype = static_cast<const list_type_impl*>(column.type.get());
|
|
|
|
// Note: below, we will call 'contains' on this toDiscard list for each element of existingList.
|
|
// Meaning that if toDiscard is big, converting it to a HashSet might be more efficient. However,
|
|
// the read-before-write this operation requires limits its usefulness on big lists, so in practice
|
|
// toDiscard will be small and keeping a list will be more efficient.
|
|
auto&& to_discard = expr::get_list_elements(lvalue);
|
|
collection_mutation_description mnew;
|
|
auto ensure = [] (const managed_bytes_opt& v) {
|
|
if (!v) {
|
|
// Note: for discarder operation, we might just ignore NULLs
|
|
throw exceptions::invalid_request_exception("Invalid NULL value in list");
|
|
}
|
|
};
|
|
for (auto&& cell : elist) {
|
|
auto has_value = [&] (bytes_view value) {
|
|
return std::find_if(to_discard.begin(), to_discard.end(),
|
|
[ltype, value, &ensure] (auto&& v) { ensure(v); return ltype->get_elements_type()->equal(*v, value); })
|
|
!= to_discard.end();
|
|
};
|
|
bytes eidx = cell.first.type()->decompose(cell.first);
|
|
bytes value = cell.second.type()->decompose(cell.second);
|
|
if (has_value(value)) {
|
|
mnew.cells.emplace_back(std::move(eidx), params.make_dead_cell());
|
|
}
|
|
}
|
|
m.set_cell(prefix, column, mnew.serialize(*ltype));
|
|
}
|
|
|
|
bool
|
|
lists::discarder_by_index::requires_read() const {
|
|
return true;
|
|
}
|
|
|
|
void
|
|
lists::discarder_by_index::execute(mutation& m, const clustering_key_prefix& prefix, const update_parameters& params) {
|
|
assert(column.type->is_multi_cell()); // "Attempted to delete an item by index from a frozen list";
|
|
cql3::raw_value index = expr::evaluate(*_e, params._options);
|
|
if (index.is_null()) {
|
|
throw exceptions::invalid_request_exception("Invalid null value for list index");
|
|
}
|
|
|
|
auto&& existing_list_opt = params.get_prefetched_list(m.key(), prefix, column);
|
|
int32_t idx = index.view().deserialize<int32_t>(*int32_type);
|
|
|
|
if (!existing_list_opt) {
|
|
throw exceptions::invalid_request_exception("Attempted to delete an element from a list which is null");
|
|
}
|
|
auto&& existing_list = *existing_list_opt;
|
|
if (idx < 0 || size_t(idx) >= existing_list.size()) {
|
|
throw exceptions::invalid_request_exception(format("List index {:d} out of bound, list has size {:d}", idx, existing_list.size()));
|
|
}
|
|
collection_mutation_description mut;
|
|
const data_value& eidx_dv = existing_list[idx].first;
|
|
bytes eidx = eidx_dv.type()->decompose(eidx_dv);
|
|
mut.cells.emplace_back(std::move(eidx), params.make_dead_cell());
|
|
m.set_cell(prefix, column, mut.serialize(*column.type));
|
|
}
|
|
|
|
}
|