diff --git a/cql3/expr/expression.cc b/cql3/expr/expression.cc index a3d3d8810f..001912ab2a 100644 --- a/cql3/expr/expression.cc +++ b/cql3/expr/expression.cc @@ -1028,6 +1028,21 @@ bool has_supporting_index( support); } +bool index_supports_some_column( + const expression& e, + const secondary_index::secondary_index_manager& index_manager, + allow_local_index allow_local) { + single_column_restrictions_map single_col_restrictions = get_single_column_restrictions_map(e); + + for (auto&& [col, col_restrictions] : single_col_restrictions) { + if (has_supporting_index(col_restrictions, index_manager, allow_local)) { + return true; + } + } + + return false; +} + std::ostream& operator<<(std::ostream& os, const column_value& cv) { os << cv.col->name_as_text(); return os; @@ -2461,5 +2476,15 @@ bool contains_multi_column_restriction(const expression& e) { }); return find_res != nullptr; } + +bool has_only_eq_binops(const expression& e) { + const expr::binary_operator* non_eq_binop = find_in_expression(e, + [](const expr::binary_operator& binop) { + return binop.op != expr::oper_t::EQ; + } + ); + + return non_eq_binop == nullptr; +} } // namespace expr } // namespace cql3 diff --git a/cql3/expr/expression.hh b/cql3/expr/expression.hh index dae8ea77dd..2562fa56b0 100644 --- a/cql3/expr/expression.hh +++ b/cql3/expr/expression.hh @@ -499,6 +499,13 @@ extern bool is_supported_by(const expression&, const secondary_index::index&); extern bool has_supporting_index( const expression&, const secondary_index::secondary_index_manager&, allow_local_index allow_local); +// Looks at each column indivudually and checks whether some index can support restrictions on this single column. +// Expression has to consist only of single column restrictions. +extern bool index_supports_some_column( + const expression&, + const secondary_index::secondary_index_manager&, + allow_local_index allow_local); + extern sstring to_string(const expression&); extern std::ostream& operator<<(std::ostream&, const column_value&); @@ -763,6 +770,8 @@ sstring get_columns_in_commons(const expression& a, const expression& b); bytes_opt value_for(const column_definition&, const expression&, const query_options&); bool contains_multi_column_restriction(const expression&); + +bool has_only_eq_binops(const expression&); } // namespace expr } // namespace cql3 diff --git a/cql3/expr/restrictions.cc b/cql3/expr/restrictions.cc index 5cad25582c..81ed6c9be6 100644 --- a/cql3/expr/restrictions.cc +++ b/cql3/expr/restrictions.cc @@ -6,12 +6,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include "restrictions.hh" #include "cql3/statements/request_validations.hh" #include "seastar/util/defer.hh" #include "cql3/prepare_context.hh" -#include "cql3/restrictions/multi_column_restriction.hh" -#include "cql3/restrictions/token_restriction.hh" +#include "types/list.hh" namespace cql3 { namespace expr { @@ -273,111 +271,5 @@ binary_operator validate_and_prepare_new_restriction(const binary_operator& rest return prepared_binop; } -namespace { - -::shared_ptr new_multi_column_restriction(const tuple_constructor& prepared_lhs_tuple, - oper_t oper, - expression prepared_rhs, - comparison_order order, - const schema_ptr& schema) { - std::vector lhs_cols = to_column_definitions(prepared_lhs_tuple.elements); - - if (oper == oper_t::EQ) { - return ::make_shared(schema, - std::move(lhs_cols), - std::move(prepared_rhs)); - } - - if (oper == oper_t::LT) { - return ::make_shared(schema, - std::move(lhs_cols), - statements::bound::END, - false, - std::move(prepared_rhs), - order); - } - - if (oper == oper_t::LTE) { - return ::make_shared(schema, - std::move(lhs_cols), - statements::bound::END, - true, - std::move(prepared_rhs), - order); - } - - if (oper == oper_t::GT) { - return ::make_shared(schema, - std::move(lhs_cols), - statements::bound::START, - false, - std::move(prepared_rhs), - order); - } - - if (oper == oper_t::GTE) { - return ::make_shared(schema, - std::move(lhs_cols), - statements::bound::START, - true, - std::move(prepared_rhs), - order); - } - - if (oper == oper_t::IN) { - if (auto rhs_marker = as_if(&prepared_rhs)) { - return ::make_shared(schema, - std::move(lhs_cols), - std::move(*rhs_marker)); - } - - if (auto rhs_list = as_if(&prepared_rhs)) { - return ::make_shared(schema, - std::move(lhs_cols), - std::move(rhs_list->elements)); - } - - if (auto rhs_list = as_if(&prepared_rhs)) { - const list_type_impl* list_type = dynamic_cast(&rhs_list->type->without_reversed()); - const data_type& elements_type = list_type->get_elements_type(); - - utils::chunked_vector raw_elems = get_list_elements(rhs_list->value); - std::vector list_elems; - list_elems.reserve(raw_elems.size()); - - for (managed_bytes elem : std::move(raw_elems)) { - raw_value elem_val = raw_value::make_value(std::move(elem)); - list_elems.emplace_back(constant(elem_val, elements_type)); - } - - return ::make_shared(schema, - std::move(lhs_cols), - std::move(list_elems)); - } - } - - on_internal_error(expr_logger, - fmt::format("new_multi_column_restriction operation type: {} not handled", oper)); -} - -} // anonymous namespace - -::shared_ptr convert_to_restriction(const binary_operator& prepared_binop, const schema_ptr& schema) { - if (is(prepared_binop.lhs) || is(prepared_binop.lhs)) { - ::shared_ptr r = ::make_shared(); - r->expression = prepared_binop; - return r; - } else if (auto lhs_tuple = as_if(&prepared_binop.lhs)) { - return new_multi_column_restriction(*lhs_tuple, prepared_binop.op, prepared_binop.rhs, prepared_binop.order, schema); - } else if (auto lhs_token = as_if(&prepared_binop.lhs)) { - std::vector column_defs = to_column_definitions(lhs_token->args); - ::shared_ptr r = ::make_shared(std::move(column_defs)); - r->expression = prepared_binop; - return r; - } else { - throw exceptions::invalid_request_exception( - format("expr::validate_and_prepare_new_restriction unhandled restriction: {}", prepared_binop)); - } -} } // namespace expr } // namespace cql3 \ No newline at end of file diff --git a/cql3/expr/restrictions.hh b/cql3/expr/restrictions.hh index 7c3336b300..c8dbfb8057 100644 --- a/cql3/expr/restrictions.hh +++ b/cql3/expr/restrictions.hh @@ -17,12 +17,5 @@ binary_operator validate_and_prepare_new_restriction(const binary_operator& rest data_dictionary::database db, schema_ptr schema, prepare_context& ctx); - - -// Converts a prepared binary operator to an instance of the restriction class. -// Doesn't perform any any validation checks. -::shared_ptr convert_to_restriction(const binary_operator& prepared_binop, - const schema_ptr& schema); - } // namespace expr } // namespace cql3 diff --git a/cql3/restrictions/bounds_slice.hh b/cql3/restrictions/bounds_slice.hh index 9dd93652c2..454a0aa0ff 100644 --- a/cql3/restrictions/bounds_slice.hh +++ b/cql3/restrictions/bounds_slice.hh @@ -10,11 +10,11 @@ #pragma once -#include "cql3/restrictions/restriction.hh" #include #include "to_string.hh" #include "exceptions/exceptions.hh" #include "index/secondary_index_manager.hh" +#include "cql3/expr/expression.hh" namespace cql3 { @@ -41,6 +41,26 @@ public: } } + static bounds_slice from_binary_operator(const expr::binary_operator& binop) { + if (binop.op == expr::oper_t::LT) { + return new_instance(statements::bound::END, false, binop.rhs); + } + + if (binop.op == expr::oper_t::LTE) { + return new_instance(statements::bound::END, true, binop.rhs); + } + + if (binop.op == expr::oper_t::GT) { + return new_instance(statements::bound::START, false, binop.rhs); + } + + if (binop.op == expr::oper_t::GTE) { + return new_instance(statements::bound::START, true, binop.rhs); + } + + throw std::runtime_error(format("bounds_slice::from_binary_operator - invalid operator: {}", binop.op)); + } + /** * Checks if this slice has a boundary for the specified type. * diff --git a/cql3/restrictions/multi_column_restriction.hh b/cql3/restrictions/multi_column_restriction.hh deleted file mode 100644 index cef5db0934..0000000000 --- a/cql3/restrictions/multi_column_restriction.hh +++ /dev/null @@ -1,443 +0,0 @@ -/* - * Copyright (C) 2015-present ScyllaDB - * - * Modified by ScyllaDB - */ - -/* - * SPDX-License-Identifier: (AGPL-3.0-or-later and Apache-2.0) - */ - -#pragma once - -#include "cql3/statements/request_validations.hh" -#include "cql3/restrictions/primary_key_restrictions.hh" -#include "cql3/statements/request_validations.hh" -#include "cql3/restrictions/single_column_primary_key_restrictions.hh" -#include "cql3/restrictions/bounds_slice.hh" -#include "cql3/constants.hh" -#include "cql3/lists.hh" -#include "cql3/expr/expression.hh" -#include "types/list.hh" -#include "types/tuple.hh" - -namespace cql3 { - -namespace restrictions { - -inline -expr::tuple_constructor -column_definitions_as_tuple_constructor(const std::vector& defs) { - std::vector columns; - std::vector column_types; - columns.reserve(defs.size()); - for (auto& def : defs) { - columns.push_back(expr::column_value{def}); - column_types.push_back(def->type); - } - data_type ttype = tuple_type_impl::get_instance(std::move(column_types)); - return expr::tuple_constructor{std::move(columns), std::move(ttype)}; -} - -class multi_column_restriction : public clustering_key_restrictions { -private: - bool _has_only_asc_columns; - bool _has_only_desc_columns; -protected: - schema_ptr _schema; - std::vector _column_defs; -public: - multi_column_restriction(schema_ptr schema, std::vector&& defs) - : _schema(schema) - , _column_defs(std::move(defs)) - { - update_asc_desc_existence(); - } - - virtual std::vector get_column_defs() const override { - return _column_defs; - } - - virtual void merge_with(::shared_ptr other) override { - const auto as_pkr = dynamic_pointer_cast(other); - statements::request_validations::check_true(bool(as_pkr), - "Mixing single column relations and multi column relations on clustering columns is not allowed"); - do_merge_with(as_pkr); - update_asc_desc_existence(); - expression = make_conjunction(std::move(expression), other->expression); - } - -protected: - virtual void do_merge_with(::shared_ptr other) = 0; - - /** - * Returns the names of the columns that are specified within this Restrictions and the other one - * as a comma separated String. - * - * @param otherRestrictions the other restrictions - * @return the names of the columns that are specified within this Restrictions and the other one - * as a comma separated String. - */ - sstring get_columns_in_commons(::shared_ptr other) const { - auto ours = get_column_defs(); - auto theirs = other->get_column_defs(); - - std::sort(ours.begin(), ours.end()); - std::sort(theirs.begin(), theirs.end()); - std::vector common; - std::set_intersection(ours.begin(), ours.end(), theirs.begin(), theirs.end(), std::back_inserter(common)); - - sstring str; - for (auto&& c : common) { - if (!str.empty()) { - str += " ,"; - } - str += c->name_as_text(); - } - return str; - } - - virtual bool has_supporting_index(const secondary_index::secondary_index_manager& index_manager, - expr::allow_local_index allow_local) const override { - for (const auto& index : index_manager.list_indexes()) { - if (!allow_local && index.metadata().local()) { - continue; - } - if (is_supported_by(index)) - return true; - } - return false; - } - - virtual bool is_supported_by(const secondary_index::index& index) const = 0; - - /** - * @return true if the restriction contains at least one column of each - * ordering, false otherwise. - */ - bool is_mixed_order() const { - return !is_desc_order() && !is_asc_order(); - } - - /** - * @return true if all the restricted columns ordered in descending - * order, false otherwise - */ - bool is_desc_order() const { - return _has_only_desc_columns; - } - - /** - * @return true if all the restricted columns ordered in ascending - * order, false otherwise - */ - bool is_asc_order() const { - return _has_only_asc_columns; - } - -private: - /** - * Updates the _has_only_asc_columns and _has_only_desc_columns fields. - */ - void update_asc_desc_existence() { - std::size_t num_of_desc = - std::count_if(_column_defs.begin(), _column_defs.end(), [] (const column_definition* cd) { return cd->type->is_reversed(); }); - _has_only_asc_columns = num_of_desc == 0; - _has_only_desc_columns = num_of_desc == _column_defs.size(); - } -#if 0 - /** - * Check if this type of restriction is supported for the specified column by the specified index. - * @param index the Secondary index - * - * @return true this type of restriction is supported by the specified index, - * false otherwise. - */ - protected abstract boolean isSupportedBy(SecondaryIndex index); -#endif -public: - class EQ; - class IN; - class IN_with_values; - class IN_with_marker; - - class slice; -}; - -class multi_column_restriction::EQ final : public multi_column_restriction { -private: - expr::expression _value; -public: - EQ(schema_ptr schema, std::vector defs, expr::expression value) - : multi_column_restriction(schema, std::move(defs)) - , _value(std::move(value)) - { - using namespace expr; - expression = binary_operator{ - column_definitions_as_tuple_constructor(_column_defs), oper_t::EQ, _value}; - } - - virtual bool is_supported_by(const secondary_index::index& index) const override { - for (auto* cdef : _column_defs) { - if (index.supports_expression(*cdef, expr::oper_t::EQ)) { - return true; - } - } - return false; - } - - virtual void do_merge_with(::shared_ptr other) override { - throw exceptions::invalid_request_exception(format("{} cannot be restricted by more than one relation if it includes an Equal", - get_columns_in_commons(other))); - } -#if 0 - @Override - protected boolean isSupportedBy(SecondaryIndex index) - { - return index.supportsOperator(Operator.EQ); - } -#endif - - clustering_key_prefix composite_value(const query_options& options) const { - cql3::raw_value t = expr::evaluate(_value, options); - auto values = expr::get_tuple_elements(t, *type_of(_value)); - std::vector components; - for (unsigned i = 0; i < values.size(); i++) { - auto component = statements::request_validations::check_not_null(values[i], - "Invalid null value in condition for column {}", - _column_defs.at(i)->name_as_text()); - components.emplace_back(*component); - } - return clustering_key_prefix::from_exploded(*_schema, std::move(components)); - } - -#if 0 - @Override - public final void addIndexExpressionTo(List expressions, - QueryOptions options) throws InvalidRequestException - { - Tuples.Value t = ((Tuples.Value) value.bind(options)); - List values = t.getElements(); - for (int i = 0; i < values.size(); i++) - { - ColumnDefinition columnDef = columnDefs.get(i); - ByteBuffer component = validateIndexedValue(columnDef, values.get(i)); - expressions.add(new IndexExpression(columnDef.name.bytes, Operator.EQ, component)); - } - } -#endif -}; - -class multi_column_restriction::IN : public multi_column_restriction { -public: - IN(schema_ptr schema, std::vector defs) - : multi_column_restriction(schema, std::move(defs)) - { } - - virtual bool is_supported_by(const secondary_index::index& index) const override { - for (auto* cdef : _column_defs) { - if (index.supports_expression(*cdef, expr::oper_t::IN)) { - return true; - } - } - return false; - } -#if 0 - @Override - public void addIndexExpressionTo(List expressions, - QueryOptions options) throws InvalidRequestException - { - List> splitInValues = splitValues(options); - checkTrue(splitInValues.size() == 1, "IN restrictions are not supported on indexed columns"); - - List values = splitInValues.get(0); - checkTrue(values.size() == 1, "IN restrictions are not supported on indexed columns"); - - ColumnDefinition columnDef = columnDefs.get(0); - ByteBuffer component = validateIndexedValue(columnDef, values.get(0)); - expressions.add(new IndexExpression(columnDef.name.bytes, Operator.EQ, component)); - } -#endif - - virtual void do_merge_with(::shared_ptr other) override { - throw exceptions::invalid_request_exception(format("{} cannot be restricted by more than one relation if it includes a IN", - get_columns_in_commons(other))); - } - -#if 0 - @Override - protected boolean isSupportedBy(SecondaryIndex index) - { - return index.supportsOperator(Operator.IN); - } -#endif -}; - -/** - * An IN restriction that has a set of terms for in values. - * For example: "SELECT ... WHERE (a, b, c) IN ((1, 2, 3), (4, 5, 6))" or "WHERE (a, b, c) IN (?, ?)" - */ -class multi_column_restriction::IN_with_values final : public multi_column_restriction::IN { -private: - std::vector _value; -public: - IN_with_values(schema_ptr schema, std::vector defs, std::vector value) - : multi_column_restriction::IN(schema, std::move(defs)) - , _value(std::move(value)) - { - std::vector column_types; - column_types.reserve(defs.size()); - for (const column_definition* cdef : defs) { - column_types.push_back(cdef->type); - } - - // type of the delayed_value is frozen list of tuples - data_type list_elements_type = tuple_type_impl::get_instance(std::move(column_types)); - data_type in_list_type = list_type_impl::get_instance(std::move(list_elements_type), false); - - expr::collection_constructor values_list { - .style = expr::collection_constructor::style_type::list, - .elements = _value, - .type = std::move(in_list_type) - }; - - using namespace expr; - expression = binary_operator{ - column_definitions_as_tuple_constructor(_column_defs), - oper_t::IN, - std::move(values_list)}; - } -}; - - -/** - * An IN restriction that uses a single marker for a set of IN values that are tuples. - * For example: "SELECT ... WHERE (a, b, c) IN ?" - */ -class multi_column_restriction::IN_with_marker final : public multi_column_restriction::IN { -private: - expr::bind_variable _marker; -public: - IN_with_marker(schema_ptr schema, std::vector defs, expr::bind_variable marker) - : IN(schema, std::move(defs)), _marker(marker) { - using namespace expr; - expression = binary_operator{ - column_definitions_as_tuple_constructor(_column_defs), - oper_t::IN, - expr::expression(std::move(marker))}; - } -}; - -class multi_column_restriction::slice final : public multi_column_restriction { - using restriction_shared_ptr = ::shared_ptr; - using mode = expr::comparison_order; - bounds_slice _slice; - mode _mode; - - slice(schema_ptr schema, std::vector defs, bounds_slice slice, mode m) - : multi_column_restriction(schema, std::move(defs)) - , _slice(slice) - , _mode(m) - { } -public: - slice(schema_ptr schema, std::vector defs, statements::bound bound, bool inclusive, expr::expression e, mode m = mode::cql) - : slice(schema, defs, bounds_slice::new_instance(bound, inclusive, e), m) - { - expression = expr::binary_operator{ - column_definitions_as_tuple_constructor(defs), - expr::pick_operator(bound, inclusive), - std::move(e), - m}; - } - - virtual bool is_supported_by(const secondary_index::index& index) const override { - for (auto* cdef : _column_defs) { - if (_slice.is_supported_by(*cdef, index)) { - return true; - } - } - return false; - } -#if 0 - @Override - public void addIndexExpressionTo(List expressions, - QueryOptions options) throws InvalidRequestException - { - throw invalidRequest("Slice restrictions are not supported on indexed columns which are part of a multi column relation"); - } - - @Override - protected boolean isSupportedBy(SecondaryIndex index) - { - return slice.isSupportedBy(index); - } - - private static Composite.EOC eocFor(Restriction r, Bound eocBound, Bound inclusiveBound) - { - if (eocBound.isStart()) - return r.isInclusive(inclusiveBound) ? Composite.EOC.NONE : Composite.EOC.END; - - return r.isInclusive(inclusiveBound) ? Composite.EOC.END : Composite.EOC.START; - } -#endif -public: - virtual void do_merge_with(::shared_ptr other) override { - using namespace statements::request_validations; - check_true(has_slice(other->expression), - "Column \"{}\" cannot be restricted by both an equality and an inequality relation", - get_columns_in_commons(other)); - auto other_slice = static_pointer_cast(other); - - static auto mode2str = [](auto m) { return m == mode::cql ? "plain" : "SCYLLA_CLUSTERING_BOUND"; }; - check_true(other_slice->_mode == this->_mode, - "Invalid combination of restrictions ({} / {})", - mode2str(this->_mode), mode2str(other_slice->_mode) - ); - check_false(_slice.has_bound(statements::bound::START) && other_slice->_slice.has_bound(statements::bound::START), - "More than one restriction was found for the start bound on {}", - get_columns_in_commons(other)); - check_false(_slice.has_bound(statements::bound::END) && other_slice->_slice.has_bound(statements::bound::END), - "More than one restriction was found for the end bound on {}", - get_columns_in_commons(other)); - - if (_column_defs.size() < other_slice->_column_defs.size()) { - _column_defs = other_slice->_column_defs; - } - _slice.merge(other_slice->_slice); - } - -private: - /** - * The function returns the first real inequality component. - * The first real inequality is the index of the first component in the - * tuple that will turn into a slice single column restriction. - * For example: (a, b, c) > (0, 1, 2) and (a, b, c) < (0, 1, 5) will be - * broken into one single column restriction set of the form: - * a = 0 and b = 1 and c > 2 and c < 5 , c is the first element that has - * inequality so for this case the function will return 2. - * @param start_components - the components of the starts tuple range. - * @param end_components - the components of the end tuple range. - * @return an empty value if not found and the index of the first index that - * will yield inequality - */ - std::optional find_first_neq_component(std::vector& start_components, - std::vector& end_components) const { - size_t common_components_count = std::min(start_components.size(), end_components.size()); - for (size_t i = 0; i < common_components_count ; i++) { - if (start_components[i].value() != end_components[i].value()) { - return i; - } - } - - size_t max_components_count = std::max(start_components.size(), end_components.size()); - if (common_components_count < max_components_count) { - return common_components_count; - } else { - return std::nullopt; - } - } -}; - -} - -} diff --git a/cql3/restrictions/primary_key_restrictions.hh b/cql3/restrictions/primary_key_restrictions.hh deleted file mode 100644 index 4f2078fc1a..0000000000 --- a/cql3/restrictions/primary_key_restrictions.hh +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2015-present ScyllaDB - * - * Modified by ScyllaDB - */ - -/* - * SPDX-License-Identifier: (AGPL-3.0-or-later and Apache-2.0) - */ - -#pragma once - -#include - -#include "cql3/query_options.hh" -#include "cql3/statements/bound.hh" -#include "cql3/restrictions/restrictions.hh" -#include "cql3/restrictions/restriction.hh" -#include "cql3/restrictions/restriction.hh" -#include "types.hh" -#include "query-request.hh" -#include - -namespace cql3 { -namespace restrictions { - -/** - * A set of restrictions on a primary key part (partition key or clustering key). - * - * What was in AbstractPrimaryKeyRestrictions was moved here (In pre 1.8 Java interfaces could not have default - * implementations of methods). - */ - -class partition_key_restrictions: public restriction, public restrictions, public enable_shared_from_this { -public: - partition_key_restrictions() = default; - - virtual void merge_with(::shared_ptr other) = 0; - - virtual ::shared_ptr merge_to(schema_ptr, ::shared_ptr restriction) { - merge_with(restriction); - return this->shared_from_this(); - } - - using restrictions::has_supporting_index; - - bool empty() const override { - return get_column_defs().empty(); - } - uint32_t size() const override { - return uint32_t(get_column_defs().size()); - } - - bool has_unrestricted_components(const schema& schema) const { - return size() < schema.partition_key_size(); - } - - virtual bool needs_filtering(const schema& schema) const { - return !empty() && !has_token(expression) && - (has_unrestricted_components(schema) || has_slice_or_needs_filtering(expression)); - } - - // NOTICE(sarna): This function is useless for partition key restrictions, - // but it should remain here until single_column_primary_key_restrictions class is detemplatized. - virtual unsigned int num_prefix_columns_that_need_not_be_filtered() const { - return 0; - } - - virtual bool is_all_eq() const { - return false; - } - - size_t prefix_size(const schema&) const { - return 0; - } -}; - -class clustering_key_restrictions : public restriction, public restrictions, public enable_shared_from_this { -public: - clustering_key_restrictions() = default; - - virtual void merge_with(::shared_ptr other) = 0; - - virtual ::shared_ptr merge_to(schema_ptr, ::shared_ptr restriction) { - merge_with(restriction); - return this->shared_from_this(); - } - - using restrictions::has_supporting_index; - - bool empty() const override { - return get_column_defs().empty(); - } - uint32_t size() const override { - return uint32_t(get_column_defs().size()); - } - - bool has_unrestricted_components(const schema& schema) const { - return size() < schema.clustering_key_size(); - } - - virtual bool needs_filtering(const schema& schema) const { - return false; - } - - // How long a prefix of the restrictions could have resulted in - // need_filtering() == false. These restrictions do not need to be - // applied during filtering. - // For example, if we have the filter "c1 < 3 and c2 > 3", c1 does - // not need filtering (just a read stopping at c1=3) but c2 does, - // so num_prefix_columns_that_need_not_be_filtered() will be 1. - virtual unsigned int num_prefix_columns_that_need_not_be_filtered() const { - return 0; - } - - virtual bool is_all_eq() const { - return false; - } - - size_t prefix_size(const schema& schema) const { - size_t count = 0; - if (schema.clustering_key_columns().empty()) { - return count; - } - auto column_defs = get_column_defs(); - column_id expected_column_id = schema.clustering_key_columns().begin()->id; - for (auto&& cdef : column_defs) { - if (schema.position(*cdef) != expected_column_id) { - return count; - } - expected_column_id++; - count++; - } - return count; - } -}; - -// FIXME(sarna): transitive hack only, do not judge. Should be dropped after all primary_key_restrictions uses are removed from code. -template -using primary_key_restrictions = std::conditional_t, partition_key_restrictions, clustering_key_restrictions>; - - -} -} diff --git a/cql3/restrictions/restriction.hh b/cql3/restrictions/restriction.hh deleted file mode 100644 index 8a397bf5f5..0000000000 --- a/cql3/restrictions/restriction.hh +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2019-present ScyllaDB - * - * Modified by ScyllaDB - */ - -/* - * SPDX-License-Identifier: (AGPL-3.0-or-later and Apache-2.0) - */ - -#pragma once - -#include "cql3/expr/expression.hh" - -namespace cql3 { - -namespace restrictions { - -/** - * Result of relation::to_restriction(). TODO: remove this class and rewrite to_restriction to return - * expression. - */ -class restriction { -public: - // Init to false for now, to easily detect errors. This whole class is going away. - cql3::expr::expression expression = expr::constant::make_bool(false); - virtual ~restriction() {} -}; - -} - -} diff --git a/cql3/restrictions/restrictions.hh b/cql3/restrictions/restrictions.hh deleted file mode 100644 index ada4cf405e..0000000000 --- a/cql3/restrictions/restrictions.hh +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2015-present ScyllaDB - * - * Modified by ScyllaDB - */ - -/* - * SPDX-License-Identifier: (AGPL-3.0-or-later and Apache-2.0) - */ - -#pragma once - -#include - -#include "cql3/query_options.hh" -#include "types.hh" -#include "schema_fwd.hh" -#include "index/secondary_index_manager.hh" -#include "restriction.hh" - -namespace cql3 { - -namespace restrictions { - -/** - * Sets of restrictions - */ -class restrictions { -public: - virtual ~restrictions() {} - - /** - * Returns the column definitions in position order. - * @return the column definitions in position order. - */ - virtual std::vector get_column_defs() const = 0; - - virtual bytes_opt value_for(const column_definition& cdef, const query_options& options) const { - throw exceptions::invalid_request_exception("Single value can be obtained from single-column restrictions only"); - } - - /** - * Check if the restriction is on indexed columns. - * - * @param index_manager the index manager - * @return true if the restriction is on indexed columns, false - */ - virtual bool has_supporting_index(const secondary_index::secondary_index_manager& index_manager, - expr::allow_local_index allow_local) const = 0; - -#if 0 - /** - * Adds to the specified list the index_expressions corresponding to this Restriction. - * - * @param expressions the list to add the index_expressions to - * @param options the query options - * @throws InvalidRequestException if this Restriction cannot be converted into - * index_expressions - */ - virtual void add_index_expression_to(std::vector<::shared_ptr>& expressions, - const query_options& options) = 0; -#endif - - /** - * Checks if this SingleColumnprimary_key_restrictions is empty or not. - * - * @return true if this SingleColumnprimary_key_restrictions is empty, false otherwise. - */ - virtual bool empty() const = 0; - - /** - * Returns the number of columns that have a restriction. - * - * @return the number of columns that have a restriction. - */ - virtual uint32_t size() const = 0; -}; - -} - -} diff --git a/cql3/restrictions/single_column_primary_key_restrictions.hh b/cql3/restrictions/single_column_primary_key_restrictions.hh deleted file mode 100644 index 898e2e41c5..0000000000 --- a/cql3/restrictions/single_column_primary_key_restrictions.hh +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (C) 2015-present ScyllaDB - * - * Modified by ScyllaDB - */ - -/* - * SPDX-License-Identifier: (AGPL-3.0-or-later and Apache-2.0) - */ - -#pragma once - -#include -#include -#include "schema_fwd.hh" -#include "cartesian_product.hh" -#include "cql3/restrictions/primary_key_restrictions.hh" -#include "cql3/restrictions/single_column_restrictions.hh" -#include "cql3/cql_config.hh" -#include "clustering_bounds_comparator.hh" -#include -#include -#include - -namespace cql3 { - -namespace restrictions { - -namespace { - -template -const char* -restricted_component_name_v; - -template <> -const char* restricted_component_name_v = "partition key"; - -template <> -const char* restricted_component_name_v = "clustering key"; - - -inline -void check_cartesian_product_size(size_t size, size_t max, const char* component_name) { - if (size > max) { - throw std::runtime_error(fmt::format("{} cartesian product size {} is greater than maximum {}", - component_name, size, max)); - } -} - -} - -/** - * A set of single column restrictions on a primary key part (partition key or clustering key). - */ -template -class single_column_primary_key_restrictions : public primary_key_restrictions { - using range_type = query::range; - using range_bound = typename range_type::bound; - template - friend class single_column_primary_key_restrictions; -private: - schema_ptr _schema; - bool _allow_filtering; - ::shared_ptr _restrictions; -private: - static uint32_t max_cartesian_product_size(const restrictions_config& config); -public: - single_column_primary_key_restrictions(schema_ptr schema, bool allow_filtering) - : _schema(schema) - , _allow_filtering(allow_filtering) - , _restrictions(::make_shared(schema)) - { - this->expression = expr::conjunction{}; // This will track _restrictions, which is a conjunction. - } - - // Convert another primary key restrictions type into this type, possibly using different schema - template - explicit single_column_primary_key_restrictions(schema_ptr schema, const single_column_primary_key_restrictions& other) - : _schema(schema) - , _allow_filtering(other._allow_filtering) - , _restrictions(::make_shared(schema)) - { - for (const auto& entry : other.restrictions()) { - const column_definition* other_cdef = entry.first; - const column_definition* this_cdef = _schema->get_column_definition(other_cdef->name()); - if (!this_cdef) { - throw exceptions::invalid_request_exception(format("Base column {} not found in view index schema", other_cdef->name_as_text())); - } - auto r = ::make_shared(*this_cdef); - r->expression = replace_column_def(entry.second->expression, this_cdef); - _restrictions->add_restriction(r); - } - } - - virtual bool is_all_eq() const override { - return _restrictions->is_all_eq(); - } - - void do_merge_with(const ::shared_ptr& single_column_restriction) { - if (!_restrictions->empty() && !_allow_filtering) { - auto last_column = *_restrictions->last_column(); - auto new_column = *get_the_only_column(single_column_restriction->expression).col; - - if (has_slice(this->expression) && _schema->position(new_column) > _schema->position(last_column)) { - throw exceptions::invalid_request_exception(format("Clustering column \"{}\" cannot be restricted (preceding column \"{}\" is restricted by a non-EQ relation)", - new_column.name_as_text(), last_column.name_as_text())); - } - - if (_schema->position(new_column) < _schema->position(last_column)) { - if (has_slice(single_column_restriction->expression)) { - throw exceptions::invalid_request_exception(format("PRIMARY KEY column \"{}\" cannot be restricted (preceding column \"{}\" is restricted by a non-EQ relation)", - last_column.name_as_text(), new_column.name_as_text())); - } - } - } - _restrictions->add_restriction(single_column_restriction); - this->expression = make_conjunction(std::move(this->expression), single_column_restriction->expression); - } - - virtual void merge_with(::shared_ptr restriction) override { - if (find_binop(restriction->expression, [] (const expr::binary_operator& b) { - return expr::is(b.lhs); - })) { - throw exceptions::invalid_request_exception( - "Mixing single column relations and multi column relations on clustering columns is not allowed"); - } - if (has_token(restriction->expression)) { - throw exceptions::invalid_request_exception( - format("Columns \"{}\" cannot be restricted by both a normal relation and a token relation", - join(", ", get_column_defs()))); - } - do_merge_with(restriction); - } - - std::vector values_as_keys(const query_options& options) const { - std::vector> value_vector; - value_vector.reserve(_restrictions->size()); - for (auto&& e : restrictions()) { - auto&& r = e.second; - assert(!has_slice(r->expression)); - auto values = expr::as(possible_lhs_values(e.first, r->expression, options)); - if (values.empty()) { - return {}; - } - value_vector.emplace_back(std::make_move_iterator(values.begin()), std::make_move_iterator(values.end())); - } - - std::vector result; - auto size = cartesian_product_size(value_vector); - check_cartesian_product_size(size, max_cartesian_product_size(options.get_cql_config().restrictions), - restricted_component_name_v); - result.reserve(size); - for (auto&& v : make_cartesian_product(value_vector)) { - result.emplace_back(ValueType::from_optional_exploded(*_schema, std::move(v))); - } - return result; - } - -public: - std::vector values(const query_options& options) const { - auto src = values_as_keys(options); - std::vector res; - for (const ValueType& r : src) { - for (const auto& component : r.components()) { - res.emplace_back(to_bytes(component)); - } - } - return res; - } - - virtual bytes_opt value_for(const column_definition& cdef, const query_options& options) const override { - return _restrictions->value_for(cdef, options); - } - - const single_column_restrictions::restrictions_map& restrictions() const { - return _restrictions->restrictions(); - } - - virtual bool has_supporting_index(const secondary_index::secondary_index_manager& index_manager, - expr::allow_local_index allow_local) const override { - return _restrictions->has_supporting_index(index_manager, allow_local); - } - -#if 0 - virtual void addIndexExpressionTo(List expressions, QueryOptions options) override { - restrictions.addIndexExpressionTo(expressions, options); - } -#endif - - virtual std::vector get_column_defs() const override { - return _restrictions->get_column_defs(); - } - - virtual bool empty() const override { - return _restrictions->empty(); - } - - virtual uint32_t size() const override { - return _restrictions->size(); - } - - virtual bool needs_filtering(const schema& schema) const override; - virtual unsigned int num_prefix_columns_that_need_not_be_filtered() const override; -}; - -template<> -inline bool single_column_primary_key_restrictions::needs_filtering(const schema& schema) const { - return primary_key_restrictions::needs_filtering(schema); -} - -// How many of the restrictions (in column order) do not need filtering -// because they are implemented as a slice (potentially, a contiguous disk -// read). For example, if we have the filter "c1 < 3 and c2 > 3", c1 does not -// need filtering but c2 does so num_prefix_columns_that_need_not_be_filtered -// will be 1. -template<> -inline unsigned single_column_primary_key_restrictions::num_prefix_columns_that_need_not_be_filtered() const { - // Restrictions currently need filtering in three cases: - // 1. any of them is a CONTAINS restriction - // 2. restrictions do not form a contiguous prefix (i.e. there are gaps in it) - // 3. a SLICE restriction isn't on a last place - column_id position = 0; - unsigned int count = 0; - for (const auto& restriction : restrictions() | boost::adaptors::map_values) { - if (find_needs_filtering(restriction->expression) - || position != get_the_only_column(restriction->expression).col->id) { - return count; - } - if (!has_slice(restriction->expression)) { - position = get_the_only_column(restriction->expression).col->id + 1; - } - count++; - } - return count; -} - -template<> -inline bool single_column_primary_key_restrictions::needs_filtering(const schema&) const { - return num_prefix_columns_that_need_not_be_filtered() < size(); -} - -template<> -inline unsigned single_column_primary_key_restrictions::num_prefix_columns_that_need_not_be_filtered() const { - // skip_filtering() is currently called only for clustering key - // restrictions, so it doesn't matter what we return here. - return 0; -} - -//TODO(sarna): These should be transformed into actual class definitions after detemplatizing single_column_primary_key_restrictions -using single_column_partition_key_restrictions = single_column_primary_key_restrictions; -using single_column_clustering_key_restrictions = single_column_primary_key_restrictions; - -template <> -inline -uint32_t single_column_primary_key_restrictions::max_cartesian_product_size(const restrictions_config& config) { - return config.partition_key_restrictions_max_cartesian_product_size; -} - -template <> -inline -uint32_t single_column_primary_key_restrictions::max_cartesian_product_size(const restrictions_config& config) { - return config.clustering_key_restrictions_max_cartesian_product_size; -} - - -} -} - - - diff --git a/cql3/restrictions/single_column_restrictions.hh b/cql3/restrictions/single_column_restrictions.hh deleted file mode 100644 index c16483b660..0000000000 --- a/cql3/restrictions/single_column_restrictions.hh +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2015-present ScyllaDB - * - * Modified by ScyllaDB - */ - -/* - * SPDX-License-Identifier: (AGPL-3.0-or-later and Apache-2.0) - */ - -#pragma once - -#include "cql3/restrictions/restrictions.hh" -#include "schema_fwd.hh" -#include "types.hh" - -namespace cql3 { - -namespace restrictions { - -/** - * Sets of single column _restrictions. - */ -class single_column_restrictions : public restrictions { -private: - /** - * The comparator used to sort the restrictions. - */ - struct column_definition_comparator { - schema_ptr _schema; - bool operator()(const column_definition* def1, const column_definition* def2) const { - auto pos1 = _schema->position(*def1); - auto pos2 = _schema->position(*def2); - if (pos1 != pos2) { - return pos1 < pos2; - } - // FIXME: shouldn't we use regular column name comparator here? Origin does not... - return less_unsigned(def1->name(), def2->name()); - } - }; - - /** - * The _restrictions per column. - */ -public: - using restrictions_map = std::map, column_definition_comparator>; -private: - restrictions_map _restrictions; - bool _is_all_eq = true; -public: - single_column_restrictions(schema_ptr schema) - : _restrictions(column_definition_comparator{std::move(schema)}) - { } - -#if 0 - @Override - public final void addIndexExpressionTo(List expressions, - QueryOptions options) throws InvalidRequestException - { - for (Restriction restriction : _restrictions.values()) - restriction.addIndexExpressionTo(expressions, options); - } -#endif - - virtual std::vector get_column_defs() const override { - std::vector r; - for (auto&& e : _restrictions) { - r.push_back(e.first); - } - return r; - } - - virtual bytes_opt value_for(const column_definition& cdef, const query_options& options) const override { - auto it = _restrictions.find(std::addressof(cdef)); - if (it == _restrictions.end()) { - return bytes_opt{}; - } else { - const auto values = std::get(possible_lhs_values(&cdef, it->second->expression, options)); - if (values.empty()) { - return bytes_opt{}; - } - assert(values.size() == 1); - return to_bytes(values.front()); - } - } - - /** - * Returns the restriction associated to the specified column. - * - * @param column_def the column definition - * @return the restriction associated to the specified column - */ - ::shared_ptr get_restriction(const column_definition& column_def) const { - auto i = _restrictions.find(&column_def); - if (i == _restrictions.end()) { - return {}; - } - return i->second; - } - - virtual bool empty() const override { - return _restrictions.empty(); - } - - virtual uint32_t size() const override { - return _restrictions.size(); - } - - /** - * Adds the specified restriction to this set of _restrictions. - * - * @param restriction the restriction to add - * @throws InvalidRequestException if the new restriction cannot be added - */ - void add_restriction(::shared_ptr restriction) { - if (!find(restriction->expression, expr::oper_t::EQ)) { - _is_all_eq = false; - } - - auto i = _restrictions.find(get_the_only_column(restriction->expression).col); - if (i == _restrictions.end()) { - _restrictions.emplace_hint(i, get_the_only_column(restriction->expression).col, std::move(restriction)); - } else { - auto& e = i->second->expression; - e = make_conjunction(std::move(e), restriction->expression); - } - } - - virtual bool has_supporting_index(const secondary_index::secondary_index_manager& index_manager, - expr::allow_local_index allow_local) const override { - for (auto&& e : _restrictions) { - if (expr::has_supporting_index(e.second->expression, index_manager, allow_local)) { - return true; - } - } - return false; - } - - /** - * Returns the column after the specified one. - * - * @param column_def the column for which the next one need to be found - * @return the column after the specified one. - */ - const column_definition* next_column(const column_definition& column_def) const { - auto i = _restrictions.find(&column_def); - if (i == _restrictions.end()) { - return nullptr; - } - ++i; - if (i == _restrictions.end()) { - return nullptr; - } - return i->first; - } - - /** - * Returns the definition of the last column. - * - * @return the definition of the last column. - */ - const column_definition* last_column() const { - if (_restrictions.empty()) { - return nullptr; - } - auto i = _restrictions.end(); - --i; - return i->first; - } - - /** - * Returns the last restriction. - * - * @return the last restriction. - */ - ::shared_ptr last_restriction() const { - if (_restrictions.empty()) { - return {}; - } - auto i = _restrictions.end(); - --i; - return i->second; - } - - const restrictions_map& restrictions() const { - return _restrictions; - } - - /** - * Checks if the _restrictions contains multiple contains, contains key, or map[key] = value. - * - * @return true if the _restrictions contains multiple contains, contains key, or , - * map[key] = value; false otherwise - */ - bool has_multiple_contains() const { - uint32_t number_of_contains = 0; - for (auto&& e : _restrictions) { - number_of_contains += count_if(e.second->expression, expr::is_on_collection); - if (number_of_contains > 1) { - return true; - } - } - return number_of_contains > 1; - } - - bool is_all_eq() const { - return _is_all_eq; - } -}; - -} -} diff --git a/cql3/restrictions/statement_restrictions.cc b/cql3/restrictions/statement_restrictions.cc index 7193e0ca1c..ade3d5e1b7 100644 --- a/cql3/restrictions/statement_restrictions.cc +++ b/cql3/restrictions/statement_restrictions.cc @@ -19,10 +19,9 @@ #include "cql3/expr/expression.hh" #include "query-result-reader.hh" #include "statement_restrictions.hh" -#include "multi_column_restriction.hh" -#include "token_restriction.hh" #include "data_dictionary/data_dictionary.hh" #include "cartesian_product.hh" +#include "cql3/cql_config.hh" #include "cql3/constants.hh" #include "cql3/lists.hh" @@ -31,7 +30,6 @@ #include "types/list.hh" #include "types/map.hh" #include "types/set.hh" -#include "cql3/expr/restrictions.hh" namespace cql3 { namespace restrictions { @@ -42,80 +40,8 @@ using boost::adaptors::filtered; using boost::adaptors::transformed; using statements::request_validations::invalid_request; -template -class statement_restrictions::initial_key_restrictions : public primary_key_restrictions { - bool _allow_filtering; -public: - initial_key_restrictions(bool allow_filtering) - : _allow_filtering(allow_filtering) { - this->expression = expr::constant::make_bool(true); - } - - ::shared_ptr> do_merge_to(schema_ptr schema, ::shared_ptr restriction) const { - return ::make_shared>(schema, _allow_filtering)->merge_to(schema, restriction); - } - ::shared_ptr> merge_to(schema_ptr schema, ::shared_ptr restriction) override { - if (has_token(restriction->expression)) { - return static_pointer_cast(restriction); - } - return ::make_shared>(schema, _allow_filtering)->merge_to(restriction); - } - void merge_with(::shared_ptr restriction) override { - throw exceptions::unsupported_operation_exception(); - } - bytes_opt value_for(const column_definition& cdef, const query_options& options) const override { - return {}; - } - std::vector get_column_defs() const override { - // throw? should not reach? - return {}; - } - bool empty() const override { - return true; - } - uint32_t size() const override { - return 0; - } - virtual bool has_supporting_index(const secondary_index::secondary_index_manager& index_manager, - expr::allow_local_index allow_local) const override { - return false; - } -}; - -template<> -::shared_ptr> -statement_restrictions::initial_key_restrictions::merge_to(schema_ptr schema, ::shared_ptr restriction) { - if (has_token(restriction->expression)) { - return static_pointer_cast(restriction); - } - return do_merge_to(std::move(schema), std::move(restriction)); -} - -template<> -::shared_ptr> -statement_restrictions::initial_key_restrictions::merge_to(schema_ptr schema, ::shared_ptr restriction) { - if (auto p = dynamic_pointer_cast(restriction)) { - return p; - } - return do_merge_to(std::move(schema), std::move(restriction)); -} - -::shared_ptr statement_restrictions::get_initial_partition_key_restrictions(bool allow_filtering) { - static thread_local ::shared_ptr initial_kr_true = ::make_shared>(true); - static thread_local ::shared_ptr initial_kr_false = ::make_shared>(false); - return allow_filtering ? initial_kr_true : initial_kr_false; -} - -::shared_ptr statement_restrictions::get_initial_clustering_key_restrictions(bool allow_filtering) { - static thread_local ::shared_ptr initial_kr_true = ::make_shared>(true); - static thread_local ::shared_ptr initial_kr_false = ::make_shared>(false); - return allow_filtering ? initial_kr_true : initial_kr_false; -} - statement_restrictions::statement_restrictions(schema_ptr schema, bool allow_filtering) : _schema(schema) - , _clustering_columns_restrictions(get_initial_clustering_key_restrictions(allow_filtering)) - , _nonprimary_key_restrictions(::make_shared(schema)) , _partition_range_is_simple(true) { } #if 0 @@ -419,21 +345,6 @@ statement_restrictions::statement_restrictions(data_dictionary::database db, add_restriction(prepared_restriction, schema, allow_filtering, for_view); if (prepared_restriction.op != expr::oper_t::IS_NOT) { - const auto restriction = expr::convert_to_restriction(prepared_restriction, schema); - if (dynamic_pointer_cast(restriction)) { - _clustering_columns_restrictions = _clustering_columns_restrictions->merge_to(_schema, restriction); - } else if (has_token(restriction->expression)) { - } else { - auto single = restriction; - auto& def = *get_the_only_column(single->expression).col; - if (def.is_partition_key()) { - } else if (def.is_clustering_key()) { - _clustering_columns_restrictions = _clustering_columns_restrictions->merge_to(_schema, restriction); - } else { - _nonprimary_key_restrictions->add_restriction(single); - } - } - _where = _where.has_value() ? make_conjunction(std::move(*_where), prepared_restriction) : prepared_restriction; } } @@ -442,6 +353,10 @@ statement_restrictions::statement_restrictions(data_dictionary::database db, 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)) { + _single_column_clustering_key_restrictions = expr::get_single_column_restrictions_map(_clustering_columns_restrictions); + } + _single_column_nonprimary_key_restrictions = expr::get_single_column_restrictions_map(_nonprimary_key_restrictions); _clustering_prefix_restrictions = extract_clustering_prefix_restrictions(*_where, _schema); _partition_range_restrictions = extract_partition_range(*_where, _schema); } @@ -450,12 +365,12 @@ statement_restrictions::statement_restrictions(data_dictionary::database db, const expr::allow_local_index allow_local( !has_partition_key_unrestricted_components() && partition_key_restrictions_is_all_eq()); - _has_multi_column = find_binop(_clustering_columns_restrictions->expression, expr::is_multi_column); - _has_queriable_ck_index = _clustering_columns_restrictions->has_supporting_index(sim, allow_local) + _has_multi_column = find_binop(_clustering_columns_restrictions, expr::is_multi_column); + _has_queriable_ck_index = clustering_columns_restrictions_have_supporting_index(sim, allow_local) && !type.is_delete(); _has_queriable_pk_index = parition_key_restrictions_have_supporting_index(sim, allow_local) && !type.is_delete(); - _has_queriable_regular_index = _nonprimary_key_restrictions->has_supporting_index(sim, allow_local) + _has_queriable_regular_index = expr::index_supports_some_column(_nonprimary_key_restrictions, sim, allow_local) && !type.is_delete(); // At this point, the select statement if fully constructed, but we still have a few things to validate @@ -495,9 +410,9 @@ statement_restrictions::statement_restrictions(data_dictionary::database db, _uses_secondary_indexing = true; } - if (_uses_secondary_indexing || _clustering_columns_restrictions->needs_filtering(*_schema)) { - _index_restrictions.push_back(_new_clustering_columns_restrictions); - } else if (find_binop(_clustering_columns_restrictions->expression, expr::is_on_collection)) { + if (_uses_secondary_indexing || clustering_key_restrictions_need_filtering()) { + _index_restrictions.push_back(_clustering_columns_restrictions); + } else if (find_binop(_clustering_columns_restrictions, expr::is_on_collection)) { fail(unimplemented::cause::INDEXES); #if 0 _index_restrictions.push_back(new Forwardingprimary_key_restrictions() { @@ -525,7 +440,7 @@ statement_restrictions::statement_restrictions(data_dictionary::database db, #endif } - if (!_nonprimary_key_restrictions->empty()) { + if (!expr::is_empty_restriction(_nonprimary_key_restrictions)) { if (_has_queriable_regular_index && _partition_range_is_simple) { _uses_secondary_indexing = true; } else if (!allow_filtering) { @@ -533,7 +448,7 @@ statement_restrictions::statement_restrictions(data_dictionary::database db, "thus may have unpredictable performance. If you want to execute " "this query despite the performance unpredictability, use ALLOW FILTERING"); } - _index_restrictions.push_back(_new_nonprimary_key_restrictions); + _index_restrictions.push_back(_nonprimary_key_restrictions); } if (_uses_secondary_indexing && !(for_view || allow_filtering)) { @@ -612,33 +527,23 @@ std::vector statement_restrictions::get_column_defs_fo } } } - auto single_ck_restrs = dynamic_pointer_cast(_clustering_columns_restrictions); const bool pk_has_unrestricted_components = has_partition_key_unrestricted_components(); - if (pk_has_unrestricted_components || _clustering_columns_restrictions->needs_filtering(*_schema)) { + if (pk_has_unrestricted_components || clustering_key_restrictions_need_filtering()) { column_id first_filtering_id = pk_has_unrestricted_components ? 0 : _schema->clustering_key_columns().begin()->id + - _clustering_columns_restrictions->num_prefix_columns_that_need_not_be_filtered(); - for (auto&& cdef : _clustering_columns_restrictions->get_column_defs()) { + num_clustering_prefix_columns_that_need_not_be_filtered(); + for (auto&& cdef : expr::get_sorted_column_defs(_clustering_columns_restrictions)) { const expr::expression* single_col_restr = nullptr; - if (single_ck_restrs) { - auto it = single_ck_restrs->restrictions().find(cdef); - if (it != single_ck_restrs->restrictions().end()) { - if (is_single_column_restriction(it->second->expression)) { - single_col_restr = &it->second->expression; - } - } + auto it = _single_column_partition_key_restrictions.find(cdef); + if (it != _single_column_partition_key_restrictions.end()) { + single_col_restr = &it->second; } if (cdef->id >= first_filtering_id && !column_uses_indexing(cdef, single_col_restr)) { column_defs_for_filtering.emplace_back(cdef); } } } - for (auto&& cdef : _nonprimary_key_restrictions->get_column_defs()) { - ::shared_ptr cur_restr = _nonprimary_key_restrictions->get_restriction(*cdef); - const expr::expression* single_col_restr = nullptr; - if (is_single_column_restriction(cur_restr->expression)) { - single_col_restr = &cur_restr->expression; - } - if (!column_uses_indexing(cdef, single_col_restr)) { + for (auto&& [cdef, cur_restr] : _single_column_nonprimary_key_restrictions) { + if (!column_uses_indexing(cdef, &cur_restr)) { column_defs_for_filtering.emplace_back(cdef); } } @@ -718,7 +623,7 @@ void statement_restrictions::add_token_partition_key_restriction(const expr::bin } void statement_restrictions::add_single_column_clustering_key_restriction(const expr::binary_operator& restr, schema_ptr schema, bool allow_filtering) { - if (find_binop(_new_clustering_columns_restrictions, [] (const expr::binary_operator& b) { + if (find_binop(_clustering_columns_restrictions, [] (const expr::binary_operator& b) { return expr::is(b.lhs); })) { throw exceptions::invalid_request_exception( @@ -726,10 +631,10 @@ void statement_restrictions::add_single_column_clustering_key_restriction(const } const column_definition* new_column = expr::get_the_only_column(restr).col; - const column_definition* last_column = expr::get_last_column_def(_new_clustering_columns_restrictions); + const column_definition* last_column = expr::get_last_column_def(_clustering_columns_restrictions); if (last_column != nullptr && !allow_filtering) { - if (has_slice(_new_clustering_columns_restrictions) && schema->position(*new_column) > schema->position(*last_column)) { + if (has_slice(_clustering_columns_restrictions) && schema->position(*new_column) > schema->position(*last_column)) { throw exceptions::invalid_request_exception(format("Clustering column \"{}\" cannot be restricted (preceding column \"{}\" is restricted by a non-EQ relation)", new_column->name_as_text(), last_column->name_as_text())); } @@ -742,16 +647,16 @@ void statement_restrictions::add_single_column_clustering_key_restriction(const } } - _new_clustering_columns_restrictions = expr::make_conjunction(_new_clustering_columns_restrictions, restr); + _clustering_columns_restrictions = expr::make_conjunction(_clustering_columns_restrictions, restr); } void statement_restrictions::add_multi_column_clustering_key_restriction(const expr::binary_operator& restr) { - if (expr::is_empty_restriction(_new_clustering_columns_restrictions)) { - _new_clustering_columns_restrictions = expr::make_conjunction(_new_clustering_columns_restrictions, restr); + if (expr::is_empty_restriction(_clustering_columns_restrictions)) { + _clustering_columns_restrictions = restr; return; } - if (!find_binop(_new_clustering_columns_restrictions, [] (const expr::binary_operator& b) { + if (!find_binop(_clustering_columns_restrictions, [] (const expr::binary_operator& b) { return expr::is(b.lhs); })) { throw exceptions::invalid_request_exception("Mixing single column relations and multi column relations on clustering columns is not allowed"); @@ -759,19 +664,19 @@ void statement_restrictions::add_multi_column_clustering_key_restriction(const e if (restr.op == expr::oper_t::EQ) { throw exceptions::invalid_request_exception(format("{} cannot be restricted by more than one relation if it includes an Equal", - expr::get_columns_in_commons(_new_clustering_columns_restrictions, restr))); + expr::get_columns_in_commons(_clustering_columns_restrictions, restr))); } else if (restr.op == expr::oper_t::IN) { throw exceptions::invalid_request_exception(format("{} cannot be restricted by more than one relation if it includes a IN", - expr::get_columns_in_commons(_new_clustering_columns_restrictions, restr))); + expr::get_columns_in_commons(_clustering_columns_restrictions, restr))); } else if (is_slice(restr.op)) { - if (!expr::has_slice(_new_clustering_columns_restrictions)) { + if (!expr::has_slice(_clustering_columns_restrictions)) { throw exceptions::invalid_request_exception(format("Column \"{}\" cannot be restricted by both an equality and an inequality relation", - expr::get_columns_in_commons(_new_clustering_columns_restrictions, restr))); + expr::get_columns_in_commons(_clustering_columns_restrictions, restr))); } - const expr::binary_operator* other_slice = expr::find_in_expression(_new_clustering_columns_restrictions, [](const expr::binary_operator){return true;}); + const expr::binary_operator* other_slice = expr::find_in_expression(_clustering_columns_restrictions, [](const expr::binary_operator){return true;}); if (other_slice == nullptr) { - on_internal_error(rlogger, "add_multi_column_clustering_key_restriction: _new_clustering_columns_restrictions is empty!"); + on_internal_error(rlogger, "add_multi_column_clustering_key_restriction: _clustering_columns_restrictions is empty!"); } // Don't allow to mix plain and SCYLLA_CLUSTERING_BOUND bounds @@ -798,14 +703,14 @@ void statement_restrictions::add_multi_column_clustering_key_restriction(const e expr::get_columns_in_commons(restr, *other_slice))); } - _new_clustering_columns_restrictions = expr::make_conjunction(_new_clustering_columns_restrictions, restr); + _clustering_columns_restrictions = expr::make_conjunction(_clustering_columns_restrictions, restr); } else { throw exceptions::invalid_request_exception(format("Unsupported multi-column relation: ", restr)); } } void statement_restrictions::add_single_column_nonprimary_key_restriction(const expr::binary_operator& restr) { - _new_nonprimary_key_restrictions = expr::make_conjunction(_new_nonprimary_key_restrictions, restr); + _nonprimary_key_restrictions = expr::make_conjunction(_nonprimary_key_restrictions, restr); } void statement_restrictions::process_partition_key_restrictions(bool for_view, bool allow_filtering) { @@ -845,13 +750,7 @@ bool statement_restrictions::partition_key_restrictions_is_empty() const { } bool statement_restrictions::partition_key_restrictions_is_all_eq() const { - const expr::binary_operator* non_eq_binop = find_in_expression(_partition_key_restrictions, - [](const expr::binary_operator& binop) { - return binop.op != expr::oper_t::EQ; - } - ); - - return non_eq_binop == nullptr; + return expr::has_only_eq_binops(_partition_key_restrictions); } size_t statement_restrictions::partition_key_restrictions_size() const { @@ -864,8 +763,95 @@ bool statement_restrictions::pk_restrictions_need_filtering() const { && (has_partition_key_unrestricted_components() || expr::has_slice_or_needs_filtering(_partition_key_restrictions)); } +size_t statement_restrictions::clustering_columns_restrictions_size() const { + return expr::get_sorted_column_defs(_clustering_columns_restrictions).size(); +} + +bool statement_restrictions::clustering_key_restrictions_need_filtering() const { + if (expr::contains_multi_column_restriction(_clustering_columns_restrictions)) { + return false; + } + + return num_clustering_prefix_columns_that_need_not_be_filtered() < clustering_columns_restrictions_size(); +} + bool statement_restrictions::has_unrestricted_clustering_columns() const { - return _clustering_columns_restrictions->has_unrestricted_components(*_schema); + return clustering_columns_restrictions_size() < _schema->clustering_key_size(); +} + +bool statement_restrictions::clustering_columns_restrictions_have_supporting_index( + const secondary_index::secondary_index_manager& index_manager, + expr::allow_local_index allow_local) const { + // Single column restrictions can be handled by the existing code + if (!expr::contains_multi_column_restriction(_clustering_columns_restrictions)) { + return expr::index_supports_some_column(_clustering_columns_restrictions, index_manager, allow_local); + } + + // Multi column restrictions have to be handled separately + for (const auto& index : index_manager.list_indexes()) { + if (!allow_local && index.metadata().local()) { + continue; + } + if (multi_column_clustering_restrictions_are_supported_by(index)) { + return true; + } + } + return false; +} + +bool statement_restrictions::multi_column_clustering_restrictions_are_supported_by( + const secondary_index::index& index) const { + // Slice restrictions have to be checked depending on the clustering slice + if (has_slice(_clustering_columns_restrictions)) { + bounds_slice clustering_slice = get_clustering_slice(); + + const expr::column_value* supported_column = + find_in_expression(_clustering_columns_restrictions, + [&](const expr::column_value& cval) -> bool { + return clustering_slice.is_supported_by(*cval.col, index); + } + ); + return supported_column != nullptr; + } + + // Otherwise it has to be a singe binary operator with EQ or IN. + // This is checked earlier during add_restriction. + const expr::binary_operator* single_binop = + expr::as_if(&_clustering_columns_restrictions); + if (single_binop == nullptr) { + on_internal_error(rlogger, format( + "multi_column_clustering_restrictions_are_supported_by more than one non-slice restriction: {}", + _clustering_columns_restrictions)); + } + + if (single_binop->op != expr::oper_t::IN && single_binop->op != expr::oper_t::EQ) { + on_internal_error(rlogger, format("Disallowed multi column restriction: {}", *single_binop)); + } + + const expr::column_value* supported_column = + find_in_expression(_clustering_columns_restrictions, + [&](const expr::column_value& cval) -> bool { + return index.supports_expression(*cval.col, single_binop->op); + } + ); + return supported_column != nullptr; +} + +bounds_slice statement_restrictions::get_clustering_slice() const { + std::optional result; + + expr::for_each_expression(_clustering_columns_restrictions, + [&](const expr::binary_operator& binop) { + bounds_slice cur_slice = bounds_slice::from_binary_operator(binop); + if (!result.has_value()) { + result = cur_slice; + } else { + result->merge(cur_slice); + } + } + ); + + return *result; } bool statement_restrictions::parition_key_restrictions_have_supporting_index(const secondary_index::secondary_index_manager& index_manager, @@ -875,20 +861,7 @@ bool statement_restrictions::parition_key_restrictions_have_supporting_index(con return false; } - // Otherwise those are single column restrictions - std::vector restricted_pk_columns = expr::get_sorted_column_defs(_partition_key_restrictions); - - for (const column_definition* cdef : restricted_pk_columns) { - expr::expression col_restrictions = expr::conjunction { - .children = expr::extract_single_column_restrictions_for_column(_partition_key_restrictions, *cdef) - }; - - if (expr::has_supporting_index(col_restrictions, index_manager, allow_local)) { - return true; - } - } - - return false; + return expr::index_supports_some_column(_partition_key_restrictions, index_manager, allow_local); } void statement_restrictions::process_clustering_columns_restrictions(bool for_view, bool allow_filtering) { @@ -896,18 +869,18 @@ void statement_restrictions::process_clustering_columns_restrictions(bool for_vi return; } - if (find_binop(_clustering_columns_restrictions->expression, expr::is_on_collection) + if (find_binop(_clustering_columns_restrictions, expr::is_on_collection) && !_has_queriable_ck_index && !allow_filtering) { throw exceptions::invalid_request_exception( "Cannot restrict clustering columns by a CONTAINS relation without a secondary index or filtering"); } - if (has_clustering_columns_restriction() && _clustering_columns_restrictions->needs_filtering(*_schema)) { + if (has_clustering_columns_restriction() && clustering_key_restrictions_need_filtering()) { if (_has_queriable_ck_index) { _uses_secondary_indexing = true; } else if (!allow_filtering && !for_view) { auto clustering_columns_iter = _schema->clustering_key_columns().begin(); - for (auto&& restricted_column : _clustering_columns_restrictions->get_column_defs()) { + for (auto&& restricted_column : expr::get_sorted_column_defs(_clustering_columns_restrictions)) { const column_definition* clustering_column = &(*clustering_columns_iter); ++clustering_columns_iter; if (clustering_column != restricted_column) { @@ -1692,8 +1665,8 @@ bool statement_restrictions::need_filtering() const { 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(); - non_pk_restrictions_count += _nonprimary_key_restrictions->size(); + int64_t non_pk_restrictions_count = clustering_columns_restrictions_size(); + non_pk_restrictions_count += expr::get_sorted_column_defs(_nonprimary_key_restrictions).size(); // We are querying using an index, one restriction goes to the index restriction. // If there are some restrictions other than token() and index column then we need to do filtering. @@ -1706,7 +1679,8 @@ bool statement_restrictions::need_filtering() const { // Can't calculate the token value, so a naive base-table query must be filtered. Same for any index tables, // except if there's only one restriction supported by an index. return !(npart == 1 && _has_queriable_pk_index && - _clustering_columns_restrictions->empty() && _nonprimary_key_restrictions->empty()); + expr::is_empty_restriction(_clustering_columns_restrictions) && + expr::is_empty_restriction(_nonprimary_key_restrictions)); } if (pk_restrictions_need_filtering()) { // We most likely cannot calculate token(s). Neither base-table nor index-table queries can avoid filtering. @@ -1714,24 +1688,24 @@ bool statement_restrictions::need_filtering() const { } // Now we know the partition key is either unrestricted or fully restricted. - const auto nreg = _nonprimary_key_restrictions->size(); + const auto nreg = expr::get_sorted_column_defs(_nonprimary_key_restrictions).size(); if (nreg > 1 || (nreg == 1 && !_has_queriable_regular_index)) { return true; // Regular columns are unsorted in storage and no single index suffices. } if (nreg == 1) { // Single non-key restriction supported by an index. // Will the index-table query require filtering? That depends on whether its clustering key is restricted to a // continuous range. Recall that this clustering key is (token, pk, ck) of the base table. - if (npart == 0 && _clustering_columns_restrictions->empty()) { + if (npart == 0 && expr::is_empty_restriction(_clustering_columns_restrictions)) { return false; // No clustering key restrictions => whole partitions. } - return !token_known(*this) || _clustering_columns_restrictions->needs_filtering(*_schema) + return !token_known(*this) || clustering_key_restrictions_need_filtering() // Multi-column restrictions don't require filtering when querying the base table, but the index // table has a different clustering key and may require filtering. || _has_multi_column; } // Now we know there are no nonkey restrictions. - if (dynamic_pointer_cast(_clustering_columns_restrictions)) { + if (_has_multi_column) { // Multicolumn bounds mean lexicographic order, implying a continuous clustering range. Multicolumn IN means a // finite set of continuous ranges. Multicolumn restrictions cannot currently be combined with single-column // clustering restrictions. Therefore, a continuous clustering range is guaranteed. @@ -1752,12 +1726,12 @@ bool statement_restrictions::need_filtering() const { // WHERE p = ? AND c1 = ? AND c2 LIKE ? AND c3 = ? - requires filtering // WHERE p = ? AND c1 = ? AND c2 = ? AND c3 = ? - doesn't use an index // WHERE p = ? AND c1 = ? AND c2 < ? AND c3 = ? - doesn't require filtering, but we report it does - return _clustering_columns_restrictions->size() > 1; + return clustering_columns_restrictions_size() > 1; } // Now we know that the query doesn't use an index. // The only thing that can cause filtering now are the clustering columns. - return _clustering_columns_restrictions->needs_filtering(*_schema); + return clustering_key_restrictions_need_filtering(); } void statement_restrictions::validate_secondary_index_selections(bool selects_only_static_columns) { @@ -1774,16 +1748,8 @@ const expr::single_column_restrictions_map& statement_restrictions::get_single_c /** * @return clustering key restrictions split into single column restrictions (e.g. for filtering support). */ -const single_column_restrictions::restrictions_map& statement_restrictions::get_single_column_clustering_key_restrictions() const { - static single_column_restrictions::restrictions_map empty; - auto single_restrictions = dynamic_pointer_cast(_clustering_columns_restrictions); - if (!single_restrictions) { - if (dynamic_pointer_cast>(_clustering_columns_restrictions)) { - return empty; - } - throw std::runtime_error("statement restrictions for multi-column partition key restrictions are not implemented yet"); - } - return single_restrictions->restrictions(); +const expr::single_column_restrictions_map& statement_restrictions::get_single_column_clustering_key_restrictions() const { + return _single_column_clustering_key_restrictions; } void statement_restrictions::prepare_indexed_global(const schema& idx_tbl_schema) { @@ -1863,6 +1829,38 @@ void statement_restrictions::add_clustering_restrictions_to_idx_ck_prefix(const } } +// How many of the restrictions (in column order) do not need filtering +// because they are implemented as a slice (potentially, a contiguous disk +// read). For example, if we have the filter "c1 < 3 and c2 > 3", c1 does not +// need filtering but c2 does so num_prefix_columns_that_need_not_be_filtered +// will be 1. +unsigned int statement_restrictions::num_clustering_prefix_columns_that_need_not_be_filtered() const { + if (expr::contains_multi_column_restriction(_clustering_columns_restrictions)) { + return 0; + } + + expr::single_column_restrictions_map column_restrictions = + expr::get_single_column_restrictions_map(_clustering_columns_restrictions); + + // Restrictions currently need filtering in three cases: + // 1. any of them is a CONTAINS restriction + // 2. restrictions do not form a contiguous prefix (i.e. there are gaps in it) + // 3. a SLICE restriction isn't on a last place + column_id position = 0; + unsigned int count = 0; + for (const auto& restriction : column_restrictions | boost::adaptors::map_values) { + if (find_needs_filtering(restriction) + || position != get_the_only_column(restriction).col->id) { + return count; + } + if (!has_slice(restriction)) { + position = get_the_only_column(restriction).col->id + 1; + } + count++; + } + return count; +} + std::vector statement_restrictions::get_global_index_clustering_ranges( const query_options& options, const schema& idx_tbl_schema) const { diff --git a/cql3/restrictions/statement_restrictions.hh b/cql3/restrictions/statement_restrictions.hh index 8761751782..750aa04f84 100644 --- a/cql3/restrictions/statement_restrictions.hh +++ b/cql3/restrictions/statement_restrictions.hh @@ -12,14 +12,14 @@ #include #include +#include "bounds_slice.hh" #include "cql3/expr/expression.hh" +#include "cql3/expr/restrictions.hh" #include "to_string.hh" #include "schema_fwd.hh" -#include "cql3/restrictions/restrictions.hh" -#include "cql3/restrictions/primary_key_restrictions.hh" -#include "cql3/restrictions/single_column_restrictions.hh" #include "cql3/prepare_context.hh" #include "cql3/statements/statement_type.hh" +#include "query-request.hh" namespace cql3 { @@ -33,12 +33,6 @@ class statement_restrictions { private: schema_ptr _schema; - template - class initial_key_restrictions; - - static ::shared_ptr get_initial_partition_key_restrictions(bool allow_filtering); - static ::shared_ptr get_initial_clustering_key_restrictions(bool allow_filtering); - /** * Restrictions on partitioning columns */ @@ -49,16 +43,16 @@ private: /** * Restrictions on clustering columns */ - ::shared_ptr _clustering_columns_restrictions; + expr::expression _clustering_columns_restrictions; - expr::expression _new_clustering_columns_restrictions; + expr::single_column_restrictions_map _single_column_clustering_key_restrictions; /** * Restriction on non-primary key columns (i.e. secondary index restrictions) */ - ::shared_ptr _nonprimary_key_restrictions; + expr::expression _nonprimary_key_restrictions; - expr::expression _new_nonprimary_key_restrictions; + expr::single_column_restrictions_map _single_column_nonprimary_key_restrictions; std::unordered_set _not_null_columns; @@ -153,11 +147,11 @@ public: * otherwise. */ bool clustering_key_restrictions_has_IN() const { - return find(_clustering_columns_restrictions->expression, expr::oper_t::IN); + return find(_clustering_columns_restrictions, expr::oper_t::IN); } bool clustering_key_restrictions_has_only_eq() const { - return _clustering_columns_restrictions->empty() || _clustering_columns_restrictions->is_all_eq(); + return expr::has_only_eq_binops(_clustering_columns_restrictions); } /** @@ -182,7 +176,7 @@ public: return _partition_key_restrictions; } - ::shared_ptr get_clustering_columns_restrictions() const { + const expr::expression& get_clustering_columns_restrictions() const { return _clustering_columns_restrictions; } @@ -231,6 +225,16 @@ public: bool parition_key_restrictions_have_supporting_index(const secondary_index::secondary_index_manager& index_manager, expr::allow_local_index allow_local) const; + size_t clustering_columns_restrictions_size() const; + + bool clustering_columns_restrictions_have_supporting_index( + const secondary_index::secondary_index_manager& index_manager, + expr::allow_local_index allow_local) const; + + bool multi_column_clustering_restrictions_are_supported_by(const secondary_index::index& index) const; + + bounds_slice get_clustering_slice() const; + /** * Checks if the clustering key has some unrestricted components. * @return true if the clustering key has some unrestricted components, false otherwise. @@ -264,8 +268,8 @@ private: const expr::expression& get_restrictions(column_kind kind) const { switch (kind) { case column_kind::partition_key: return _partition_key_restrictions; - case column_kind::clustering_key: return _new_clustering_columns_restrictions; - default: return _new_nonprimary_key_restrictions; + case column_kind::clustering_key: return _clustering_columns_restrictions; + default: return _nonprimary_key_restrictions; } } @@ -277,6 +281,7 @@ private: */ void add_clustering_restrictions_to_idx_ck_prefix(const schema& idx_tbl_schema); + unsigned int num_clustering_prefix_columns_that_need_not_be_filtered() const; #if 0 std::vector<::shared_ptr> get_index_expressions(const query_options& options) { if (!_uses_secondary_indexing || _index_restrictions.empty()) { @@ -440,7 +445,7 @@ public: * false otherwise. */ bool has_clustering_columns_restriction() const { - return !_clustering_columns_restrictions->empty(); + return !expr::is_empty_restriction(_clustering_columns_restrictions); } /** @@ -449,24 +454,26 @@ public: * @return true if the restrictions contain any non-primary key restrictions, false otherwise. */ bool has_non_primary_key_restriction() const { - return !_nonprimary_key_restrictions->empty(); + return !expr::is_empty_restriction(_nonprimary_key_restrictions); } bool pk_restrictions_need_filtering() const; bool ck_restrictions_need_filtering() const { - if (_clustering_columns_restrictions->empty()) { + if (expr::is_empty_restriction(_clustering_columns_restrictions)) { return false; } return has_partition_key_unrestricted_components() - || _clustering_columns_restrictions->needs_filtering(*_schema) + || clustering_key_restrictions_need_filtering() // 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(_partition_key_restrictions)); } + bool clustering_key_restrictions_need_filtering() const; + /** * @return true if column is restricted by some restriction, false otherwise */ @@ -482,8 +489,8 @@ public: /** * @return the non-primary key restrictions. */ - const single_column_restrictions::restrictions_map& get_non_pk_restriction() const { - return _nonprimary_key_restrictions->restrictions(); + const expr::single_column_restrictions_map& get_non_pk_restriction() const { + return _single_column_nonprimary_key_restrictions; } /** @@ -494,7 +501,7 @@ public: /** * @return clustering key restrictions split into single column restrictions (e.g. for filtering support). */ - const single_column_restrictions::restrictions_map& get_single_column_clustering_key_restrictions() const; + const expr::single_column_restrictions_map& get_single_column_clustering_key_restrictions() const; /// Prepares internal data for evaluating index-table queries. Must be called before /// get_local_index_clustering_ranges(). diff --git a/cql3/restrictions/token_restriction.hh b/cql3/restrictions/token_restriction.hh deleted file mode 100644 index c7e407e65e..0000000000 --- a/cql3/restrictions/token_restriction.hh +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2015-present ScyllaDB - * - * Modified by ScyllaDB - */ - -/* - * SPDX-License-Identifier: (AGPL-3.0-or-later and Apache-2.0) - */ - -#pragma once - -#include "restriction.hh" -#include "primary_key_restrictions.hh" -#include "exceptions/exceptions.hh" -#include "bounds_slice.hh" -#include "keys.hh" - -class column_definition; - -namespace cql3 { - -namespace restrictions { - -/** - * Restriction using the token function. - */ -class token_restriction: public partition_key_restrictions { -private: - /** - * The definition of the columns to which apply the token restriction. - */ - std::vector _column_definitions; -public: - token_restriction(std::vector c) - : _column_definitions(std::move(c)) { - } - - std::vector get_column_defs() const override { - return _column_definitions; - } - - void merge_with(::shared_ptr restriction) override { - if (!has_token(restriction->expression)) { - throw exceptions::invalid_request_exception( - format("Columns \"{}\" cannot be restricted by both a normal relation and a token relation", - join(", ", get_column_defs()))); - } - expression = make_conjunction(std::move(expression), restriction->expression); - } - - virtual bool has_supporting_index(const secondary_index::secondary_index_manager& index_manager, - expr::allow_local_index allow_local) const override { - return false; - } - -#if 0 - void add_index_expression_to(std::vector<::shared_ptr>& expressions, - const query_options& options) override { - throw exceptions::unsupported_operation_exception(); - } -#endif -}; - -} - -} diff --git a/cql3/selection/selection.cc b/cql3/selection/selection.cc index 7501ca5b40..9867bfb2a7 100644 --- a/cql3/selection/selection.cc +++ b/cql3/selection/selection.cc @@ -18,7 +18,6 @@ #include "cql3/selection/selector_factories.hh" #include "cql3/result_set.hh" #include "cql3/query_options.hh" -#include "cql3/restrictions/multi_column_restriction.hh" #include "cql3/restrictions/statement_restrictions.hh" namespace cql3 { @@ -431,13 +430,13 @@ bool result_set_builder::restrictions_filter::do_filter(const selection& selecti return false; } - auto clustering_columns_restrictions = _restrictions->get_clustering_columns_restrictions(); - if (dynamic_pointer_cast(clustering_columns_restrictions)) { + const expr::expression& clustering_columns_restrictions = _restrictions->get_clustering_columns_restrictions(); + if (expr::contains_multi_column_restriction(clustering_columns_restrictions)) { clustering_key_prefix ckey = clustering_key_prefix::from_exploded(clustering_key); // FIXME: push to upper layer so it happens once per row auto static_and_regular_columns = expr::get_non_pk_values(selection, static_row, row); return expr::is_satisfied_by( - clustering_columns_restrictions->expression, + clustering_columns_restrictions, expr::evaluation_inputs{ .partition_key = &partition_key, .clustering_key = &clustering_key, @@ -449,7 +448,7 @@ bool result_set_builder::restrictions_filter::do_filter(const selection& selecti auto static_row_iterator = static_row.iterator(); auto row_iterator = row ? std::optional(row->iterator()) : std::nullopt; - auto non_pk_restrictions_map = _restrictions->get_non_pk_restriction(); + const expr::single_column_restrictions_map& non_pk_restrictions_map = _restrictions->get_non_pk_restriction(); for (auto&& cdef : selection.get_columns()) { switch (cdef->kind) { case column_kind::static_column: @@ -462,11 +461,11 @@ bool result_set_builder::restrictions_filter::do_filter(const selection& selecti if (restr_it == non_pk_restrictions_map.end()) { continue; } - restrictions::restriction& single_col_restriction = *restr_it->second; + const expr::expression& single_col_restriction = restr_it->second; // FIXME: push to upper layer so it happens once per row auto static_and_regular_columns = expr::get_non_pk_values(selection, static_row, row); bool regular_restriction_matches = expr::is_satisfied_by( - single_col_restriction.expression, + single_col_restriction, expr::evaluation_inputs{ .partition_key = &partition_key, .clustering_key = &clustering_key, @@ -508,7 +507,8 @@ bool result_set_builder::restrictions_filter::do_filter(const selection& selecti if (_skip_ck_restrictions) { continue; } - auto clustering_key_restrictions_map = _restrictions->get_single_column_clustering_key_restrictions(); + const expr::single_column_restrictions_map& clustering_key_restrictions_map = + _restrictions->get_single_column_clustering_key_restrictions(); auto restr_it = clustering_key_restrictions_map.find(cdef); if (restr_it == clustering_key_restrictions_map.end()) { continue; @@ -516,9 +516,9 @@ bool result_set_builder::restrictions_filter::do_filter(const selection& selecti if (clustering_key.empty()) { return false; } - restrictions::restriction& single_col_restriction = *restr_it->second; + const expr::expression& single_col_restriction = restr_it->second; if (!expr::is_satisfied_by( - single_col_restriction.expression, + single_col_restriction, expr::evaluation_inputs{ .partition_key = &partition_key, .clustering_key = &clustering_key, diff --git a/cql3/statements/create_view_statement.cc b/cql3/statements/create_view_statement.cc index a5b96cb617..adc1b53f59 100644 --- a/cql3/statements/create_view_statement.cc +++ b/cql3/statements/create_view_statement.cc @@ -270,7 +270,7 @@ view_ptr create_view_statement::prepare_view(data_dictionary::database db) const // non-pk base column + base column used in view pk)". When the filtered // column *is* the base column added to the view pk, we don't have this // problem. And this case actually works correctly. - auto non_pk_restrictions = restrictions->get_non_pk_restriction(); + const expr::single_column_restrictions_map& non_pk_restrictions = restrictions->get_non_pk_restriction(); if (non_pk_restrictions.size() == 1 && has_non_pk_column && target_primary_keys.contains(non_pk_restrictions.cbegin()->first)) { // This case (filter by new PK column of the view) works, as explained above diff --git a/cql3/statements/delete_statement.cc b/cql3/statements/delete_statement.cc index 80bf1e7aea..88bd72c503 100644 --- a/cql3/statements/delete_statement.cc +++ b/cql3/statements/delete_statement.cc @@ -78,7 +78,7 @@ delete_statement::prepare_internal(data_dictionary::database db, schema_ptr sche } prepare_conditions(db, *schema, ctx, *stmt); stmt->process_where_clause(db, _where_clause, ctx); - if (has_slice(stmt->restrictions().get_clustering_columns_restrictions()->expression)) { + if (has_slice(stmt->restrictions().get_clustering_columns_restrictions())) { if (!schema->is_compound()) { throw exceptions::invalid_request_exception("Range deletions on \"compact storage\" schemas are not supported"); } diff --git a/cql3/statements/modification_statement.cc b/cql3/statements/modification_statement.cc index ba3f6d72e7..87f430ce2f 100644 --- a/cql3/statements/modification_statement.cc +++ b/cql3/statements/modification_statement.cc @@ -399,10 +399,10 @@ modification_statement::process_where_clause(data_dictionary::database db, std:: | boost::adaptors::transformed(std::mem_fn(&column_definition::name_as_text))); throw exceptions::invalid_request_exception(format("Invalid where clause contains non PRIMARY KEY columns: {}", column_names)); } - auto ck_restrictions = _restrictions->get_clustering_columns_restrictions(); - if (has_slice(ck_restrictions->expression) && !allow_clustering_key_slices()) { + const expr::expression& ck_restrictions = _restrictions->get_clustering_columns_restrictions(); + if (has_slice(ck_restrictions) && !allow_clustering_key_slices()) { throw exceptions::invalid_request_exception( - format("Invalid operator in where clause {}", to_string(ck_restrictions->expression))); + format("Invalid operator in where clause {}", to_string(ck_restrictions))); } if (_restrictions->has_unrestricted_clustering_columns() && !applies_only_to_static_columns() && !s->is_dense()) { // Tomek: Origin had "&& s->comparator->is_composite()" in the condition below. @@ -415,13 +415,13 @@ modification_statement::process_where_clause(data_dictionary::database db, std:: // Those tables don't have clustering columns so we wouldn't reach this code, thus // the check seems redundant. if (require_full_clustering_key()) { - auto& col = s->column_at(column_kind::clustering_key, ck_restrictions->size()); + auto& col = s->column_at(column_kind::clustering_key, _restrictions->clustering_columns_restrictions_size()); throw exceptions::invalid_request_exception(format("Missing mandatory PRIMARY KEY part {}", col.name_as_text())); } // In general, we can't modify specific columns if not all clustering columns have been specified. // However, if we modify only static columns, it's fine since we won't really use the prefix anyway. - if (!has_slice(ck_restrictions->expression)) { - auto& col = s->column_at(column_kind::clustering_key, ck_restrictions->size()); + if (!has_slice(ck_restrictions)) { + auto& col = s->column_at(column_kind::clustering_key, _restrictions->clustering_columns_restrictions_size()); for (auto&& op : _column_operations) { if (!op->column.is_static()) { throw exceptions::invalid_request_exception(format("Primary key column '{}' must be specified in order to modify column '{}'", diff --git a/cql3/statements/select_statement.cc b/cql3/statements/select_statement.cc index 35c4391744..5feedcdfd2 100644 --- a/cql3/statements/select_statement.cc +++ b/cql3/statements/select_statement.cc @@ -18,7 +18,6 @@ #include "cql3/functions/as_json_function.hh" #include "cql3/selection/selection.hh" #include "cql3/util.hh" -#include "cql3/restrictions/single_column_primary_key_restrictions.hh" #include "cql3/restrictions/statement_restrictions.hh" #include "cql3/selection/selector_factories.hh" #include "validation.hh" @@ -1948,12 +1947,12 @@ static bool needs_allow_filtering_anyway( if (strict_allow_filtering == flag_t::FALSE) { return false; } - const auto& ck_restrictions = *restrictions.get_clustering_columns_restrictions(); + const auto& ck_restrictions = restrictions.get_clustering_columns_restrictions(); 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) || has_token(pk_restrictions)) // Potentially unlimited partitions. - && !ck_restrictions.empty() // Slice defined. + && !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) { warnings.emplace_back("This query should use ALLOW FILTERING and will be rejected in future versions."); diff --git a/db/view/view.cc b/db/view/view.cc index 3965eacb5a..d11b2f4f55 100644 --- a/db/view/view.cc +++ b/db/view/view.cc @@ -256,7 +256,7 @@ bool partition_key_matches(const schema& base, const view_info& view, const dht: } bool clustering_prefix_matches(const schema& base, const view_info& view, const partition_key& key, const clustering_key_prefix& ck) { - const auto r = view.select_statement().get_restrictions()->get_clustering_columns_restrictions(); + const cql3::expr::expression& r = view.select_statement().get_restrictions()->get_clustering_columns_restrictions(); std::vector exploded_pk = key.explode(); std::vector exploded_ck = ck.explode(); std::vector ck_columns; @@ -269,7 +269,7 @@ bool clustering_prefix_matches(const schema& base, const view_info& view, const auto dummy_options = cql3::query_options({ }); // FIXME: pass nullptrs for some of theses dummies return cql3::expr::is_satisfied_by( - r->expression, + r, cql3::expr::evaluation_inputs{ .partition_key = &exploded_pk, .clustering_key = &exploded_ck, @@ -346,7 +346,7 @@ public: // FIXME: pass dummy_options as nullptr auto dummy_options = cql3::query_options({}); return cql3::expr::is_satisfied_by( - r->expression, + r, cql3::expr::evaluation_inputs{ .partition_key = &_pk, .clustering_key = &ck, diff --git a/test/boost/statement_restrictions_test.cc b/test/boost/statement_restrictions_test.cc index 74afc3c611..75d98efd2c 100644 --- a/test/boost/statement_restrictions_test.cc +++ b/test/boost/statement_restrictions_test.cc @@ -15,7 +15,6 @@ #include "cql3/util.hh" #include "test/lib/cql_assertions.hh" #include "test/lib/cql_test_env.hh" -#include "cql3/restrictions/multi_column_restriction.hh" using namespace cql3; @@ -95,6 +94,18 @@ auto both_closed(std::vector lb, std::vector ub) { return query::clustering_range({{cklb, inclusive}}, {{ckub, inclusive}}); } +expr::tuple_constructor +column_definitions_as_tuple_constructor(const std::vector& defs) { + std::vector columns; + std::vector column_types; + columns.reserve(defs.size()); + for (auto& def : defs) { + columns.push_back(expr::column_value{def}); + column_types.push_back(def->type); + } + data_type ttype = tuple_type_impl::get_instance(std::move(column_types)); + return expr::tuple_constructor{std::move(columns), std::move(ttype)}; +} } // anonymous namespace SEASTAR_TEST_CASE(slice_empty_restriction) { @@ -420,7 +431,7 @@ BOOST_AUTO_TEST_CASE(expression_extract_column_restrictions) { expression r2_restriction(binary_operator(column_value(&col_r2), oper_t::EQ, zero_value)); auto make_multi_column_restriction = [](std::vector columns, oper_t oper) -> expression { - tuple_constructor column_tuple(cql3::restrictions::column_definitions_as_tuple_constructor(columns)); + tuple_constructor column_tuple(column_definitions_as_tuple_constructor(columns)); std::vector zeros_tuple_elems(columns.size(), managed_bytes_opt(I(0))); data_type tup_type = tuple_type_impl::get_instance(std::vector(columns.size(), int32_type));