/* * 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_type.hh" #include "constants.hh" #include #include "types/list.hh" #include "utils/UUID_gen.hh" namespace cql3 { lw_shared_ptr lists::index_spec_of(const column_specification& column) { return make_lw_shared(column.ks_name, column.cf_name, ::make_shared(format("idx({})", *column.name), true), int32_type); } lw_shared_ptr lists::uuid_index_spec_of(const column_specification& column) { return make_lw_shared(column.ks_name, column.cf_name, ::make_shared(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 expr::constant& value) { if (value.is_unset_value()) { return; } 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"); } if (index.is_unset_value()) { throw exceptions::invalid_request_exception("Invalid unset value for list index"); } auto value = expr::evaluate(*_e, params._options); if (value.is_unset_value()) { return; } auto idx = index.view().deserialize(*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(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"); } if (index.is_unset_value()) { throw exceptions::invalid_request_exception("Invalid unset value for list index"); } auto ltype = static_cast(column.type.get()); collection_mutation_description mut; mut.cells.reserve(1); if (value.is_null()) { mut.cells.emplace_back(std::move(index.value).to_bytes(), params.make_dead_cell()); } else { mut.cells.emplace_back( std::move(index.value).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 expr::constant value = expr::evaluate(*_e, params._options); if (value.is_unset_value()) { return; } 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 expr::constant& 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_or_unset()) { return; } auto ltype = static_cast(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(uuid1.data()), uuid1.size()); // 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 { // 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 { 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"; expr::constant lvalue = expr::evaluate(*_e, params._options); if (lvalue.is_null_or_unset()) { return; } assert(lvalue.type->is_list()); // 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 list_elements = expr::get_list_elements(lvalue); mut.cells.reserve(list_elements.size()); auto ltype = static_cast(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++); 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. expr::constant lvalue = expr::evaluate(*_e, params._options); if (!existing_list) { return; } auto&& elist = *existing_list; if (elist.empty()) { return; } if (lvalue.is_null_or_unset()) { return; } assert(lvalue.type->is_list()); auto ltype = static_cast(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; for (auto&& cell : elist) { auto has_value = [&] (bytes_view value) { return std::find_if(to_discard.begin(), to_discard.end(), [ltype, value] (auto&& 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"; expr::constant index = expr::evaluate(*_e, params._options); if (index.is_null()) { throw exceptions::invalid_request_exception("Invalid null value for list index"); } if (index.is_unset_value()) { return; } auto&& existing_list_opt = params.get_prefetched_list(m.key(), prefix, column); int32_t idx = index.view().deserialize(*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)); } }