diff --git a/cql3/expr/expression.cc b/cql3/expr/expression.cc index 294393cd4e..14374a51a7 100644 --- a/cql3/expr/expression.cc +++ b/cql3/expr/expression.cc @@ -191,18 +191,18 @@ bool equal(term& rhs, const column_value& lhs, const column_value_eval_bag& bag) } /// True iff columns' values equal t. -bool equal(term& t, const std::vector& columns, const column_value_eval_bag& bag) { +bool equal(term& t, const column_value_tuple& columns_tuple, const column_value_eval_bag& bag) { const auto tup = get_tuple(t, bag.options); if (!tup) { throw exceptions::invalid_request_exception("multi-column equality has right-hand side that isn't a tuple"); } const auto& rhs = tup->get_elements(); - if (rhs.size() != columns.size()) { + if (rhs.size() != columns_tuple.elements.size()) { throw exceptions::invalid_request_exception( format("tuple equality size mismatch: {} elements on left-hand side, {} on right", - columns.size(), rhs.size())); + columns_tuple.elements.size(), rhs.size())); } - return boost::equal(rhs, columns, [&] (const managed_bytes_opt& b, const column_value& lhs) { + return boost::equal(rhs, columns_tuple.elements, [&] (const managed_bytes_opt& b, const column_value& lhs) { return equal(b, lhs, bag); }); } @@ -242,7 +242,7 @@ bool limits(const column_value& col, oper_t op, term& rhs, const column_value_ev } /// True iff the column values are limited by t in the manner prescribed by op. -bool limits(const std::vector& columns, const oper_t op, term& t, +bool limits(const column_value_tuple& columns_tuple, const oper_t op, term& t, const column_value_eval_bag& bag) { if (!is_slice(op)) { // For EQ or NEQ, use equal(). throw std::logic_error("limits() called on non-slice op"); @@ -253,15 +253,15 @@ bool limits(const std::vector& columns, const oper_t op, term& t, "multi-column comparison has right-hand side that isn't a tuple"); } const auto& rhs = tup->get_elements(); - if (rhs.size() != columns.size()) { + if (rhs.size() != columns_tuple.elements.size()) { throw exceptions::invalid_request_exception( format("tuple comparison size mismatch: {} elements on left-hand side, {} on right", - columns.size(), rhs.size())); + columns_tuple.elements.size(), rhs.size())); } for (size_t i = 0; i < rhs.size(); ++i) { - const auto cmp = get_value_comparator(columns[i])->compare( + const auto cmp = get_value_comparator(columns_tuple.elements[i])->compare( // CQL dictates that columns[i] is a clustering column and non-null. - *get_value(columns[i], bag), + *get_value(columns_tuple.elements[i], bag), *rhs[i]); // If the components aren't equal, then we just learned the LHS/RHS order. if (cmp < 0) { @@ -433,18 +433,18 @@ bool is_one_of(const column_value& col, term& rhs, const column_value_eval_bag& } /// True iff the tuple of column values is in the set defined by rhs. -bool is_one_of(const std::vector& cvs, term& rhs, const column_value_eval_bag& bag) { +bool is_one_of(const column_value_tuple& tuple, term& rhs, const column_value_eval_bag& bag) { // RHS is prepared differently for different CQL cases. Cast it dynamically to discern which case this is. if (auto dv = dynamic_cast(&rhs)) { // This is `(a,b) IN ((1,1),(2,2),(3,3))`. RHS elements are themselves terms. return boost::algorithm::any_of(dv->get_elements(), [&] (const ::shared_ptr& t) { - return equal(*t, cvs, bag); + return equal(*t, tuple, bag); }); } else if (auto mkr = dynamic_cast(&rhs)) { // This is `(a,b) IN ?`. RHS elements are themselves tuples, represented as vector. const auto marker_value = static_pointer_cast(mkr->bind(bag.options)); return boost::algorithm::any_of(marker_value->get_split_values(), [&] (const std::vector& el) { - return boost::equal(cvs, el, [&] (const column_value& c, const managed_bytes_opt& b) { + return boost::equal(tuple.elements, el, [&] (const column_value& c, const managed_bytes_opt& b) { return equal(b, c, bag); }); }); @@ -520,7 +520,7 @@ bool is_satisfied_by(const binary_operator& opr, const column_value_eval_bag& ba throw exceptions::unsupported_operation_exception(format("Unhandled binary_operator: {}", opr)); } }, - [&] (const std::vector& cvs) { + [&] (const column_value_tuple& cvs) { if (opr.op == oper_t::EQ) { return equal(*opr.rhs, cvs, bag); } else if (is_slice(opr.op)) { @@ -711,16 +711,16 @@ value_set possible_lhs_values(const column_definition* cdef, const expression& e } throw std::logic_error(format("possible_lhs_values: unhandled operator {}", oper)); }, - [&] (const std::vector& cvs) -> value_set { + [&] (const column_value_tuple& tuple) -> value_set { if (!cdef) { return unbounded_value_set; } const auto found = boost::find_if( - cvs, [&] (const column_value& c) { return c.col == cdef; }); - if (found == cvs.end()) { + tuple.elements, [&] (const column_value& c) { return c.col == cdef; }); + if (found == tuple.elements.end()) { return unbounded_value_set; } - const auto column_index_on_lhs = std::distance(cvs.begin(), found); + const auto column_index_on_lhs = std::distance(tuple.elements.begin(), found); if (is_compare(oper.op)) { // RHS must be a tuple due to upstream checks. managed_bytes_opt val = get_tuple(*oper.rhs, options)->get_elements()[column_index_on_lhs]; @@ -796,9 +796,9 @@ bool is_supported_by(const expression& expr, const secondary_index::index& idx) [&] (const column_value& col) { return idx.supports_expression(*col.col, oper.op); }, - [&] (const std::vector& cvs) { - if (cvs.size() == 1) { - return idx.supports_expression(*cvs[0].col, oper.op); + [&] (const column_value_tuple& tuple) { + if (tuple.elements.size() == 1) { + return idx.supports_expression(*tuple.elements[0].col, oper.op); } // We don't use index table for multi-column restrictions, as it cannot avoid filtering. return false; @@ -840,8 +840,8 @@ std::ostream& operator<<(std::ostream& os, const expression& expr) { [&] (const column_value& col) { fmt::print(os, "{}", col); }, - [&] (const std::vector& cvs) { - fmt::print(os, "({})", fmt::join(cvs, ",")); + [&] (const column_value_tuple& tuple) { + fmt::print(os, "({})", fmt::join(tuple.elements, ",")); }, }, opr.lhs); os << ' ' << opr.op << ' ' << *opr.rhs; @@ -858,8 +858,8 @@ bool is_on_collection(const binary_operator& b) { if (b.op == oper_t::CONTAINS || b.op == oper_t::CONTAINS_KEY) { return true; } - if (auto cvs = std::get_if>(&b.lhs)) { - return boost::algorithm::any_of(*cvs, [] (const column_value& v) { return v.sub; }); + if (auto tuple = std::get_if(&b.lhs)) { + return boost::algorithm::any_of(tuple->elements, [] (const column_value& v) { return v.sub; }); } return false; } @@ -877,7 +877,7 @@ expression replace_column_def(const expression& expr, const column_definition* n [&] (const column_value& col) { return expression(binary_operator{column_value{new_cdef}, oper.op, oper.rhs}); }, - [&] (const std::vector& cvs) -> expression { + [&] (const column_value_tuple& tuple) -> expression { throw std::logic_error(format("replace_column_def invalid LHS: {}", to_string(oper))); }, [&] (const token&) { return expr; }, diff --git a/cql3/expr/expression.hh b/cql3/expr/expression.hh index c1039b4476..cf133fef6f 100644 --- a/cql3/expr/expression.hh +++ b/cql3/expr/expression.hh @@ -25,6 +25,7 @@ #include #include #include +#include #include "bytes.hh" #include "cql3/query_options.hh" @@ -67,6 +68,19 @@ struct column_value { column_value(const column_definition* col, ::shared_ptr sub) : col(col), sub(sub) {} }; +/// A tuple of column selectors. Usually used for range constraints on clustering keys. +struct column_value_tuple { + std::vector elements; + explicit column_value_tuple(std::vector); + template + requires requires (Range r) { + { r.begin() } -> std::input_iterator; + { r.end() } -> std::input_iterator; + { *r.begin() } -> std::convertible_to; + } + explicit column_value_tuple(Range r); +}; + /// Represents token function on LHS of an operator relation. No need to list column definitions /// here -- token takes exactly the partition key as its argument. struct token {}; @@ -81,7 +95,7 @@ enum class comparison_order : char { /// Operator restriction: LHS op RHS. struct binary_operator { - std::variant, token> lhs; + std::variant lhs; oper_t op; ::shared_ptr rhs; comparison_order order = comparison_order::cql; @@ -223,7 +237,7 @@ inline bool is_compare(oper_t op) { } inline bool is_multi_column(const binary_operator& op) { - return holds_alternative>(op.lhs); + return holds_alternative(op.lhs); } inline bool has_token(const expression& e) { @@ -255,6 +269,22 @@ inline oper_t pick_operator(statements::bound b, bool inclusive) { (inclusive ? oper_t::LTE : oper_t::LT); } +inline +column_value_tuple::column_value_tuple(std::vector e) + : elements(std::move(e)) { +} + +template + requires requires (Range r) { + { r.begin() } -> std::input_iterator; + { r.end() } -> std::input_iterator; + { *r.begin() } -> std::convertible_to; + } +column_value_tuple::column_value_tuple(Range r) + : column_value_tuple(std::vector(r.begin(), r.end())) { +} + + } // namespace expr } // namespace cql3 diff --git a/cql3/restrictions/multi_column_restriction.hh b/cql3/restrictions/multi_column_restriction.hh index 35dd3498b1..9c88d4bf84 100644 --- a/cql3/restrictions/multi_column_restriction.hh +++ b/cql3/restrictions/multi_column_restriction.hh @@ -190,7 +190,7 @@ public: { using namespace expr; expression = binary_operator{ - std::vector(_column_defs.cbegin(), _column_defs.cend()), oper_t::EQ, _value}; + column_value_tuple(_column_defs), oper_t::EQ, _value}; } virtual bool is_supported_by(const secondary_index::index& index) const override { @@ -332,7 +332,7 @@ public: { using namespace expr; expression = binary_operator{ - std::vector(_column_defs.cbegin(), _column_defs.cend()), + column_value_tuple(_column_defs), oper_t::IN, ::make_shared(_values)}; } @@ -361,7 +361,7 @@ public: : IN(schema, std::move(defs)), _marker(marker) { using namespace expr; expression = binary_operator{ - std::vector(_column_defs.cbegin(), _column_defs.cend()), + column_value_tuple(_column_defs), oper_t::IN, std::move(marker)}; } @@ -391,7 +391,7 @@ public: : slice(schema, defs, term_slice::new_instance(bound, inclusive, term), m) { expression = expr::binary_operator{ - std::vector(defs.cbegin(), defs.cend()), + expr::column_value_tuple(defs), expr::pick_operator(bound, inclusive), std::move(term), m}; diff --git a/cql3/restrictions/single_column_primary_key_restrictions.hh b/cql3/restrictions/single_column_primary_key_restrictions.hh index 505ea320a2..494b5b3043 100644 --- a/cql3/restrictions/single_column_primary_key_restrictions.hh +++ b/cql3/restrictions/single_column_primary_key_restrictions.hh @@ -171,7 +171,7 @@ public: virtual void merge_with(::shared_ptr restriction) override { if (find_atom(restriction->expression, [] (const expr::binary_operator& b) { - return std::holds_alternative>(b.lhs); + return std::holds_alternative(b.lhs); })) { throw exceptions::invalid_request_exception( "Mixing single column relations and multi column relations on clustering columns is not allowed"); diff --git a/cql3/restrictions/statement_restrictions.cc b/cql3/restrictions/statement_restrictions.cc index a7bb9ad71b..b94075bb49 100644 --- a/cql3/restrictions/statement_restrictions.cc +++ b/cql3/restrictions/statement_restrictions.cc @@ -173,7 +173,7 @@ static std::vector extract_clustering_prefix_restrictions( } void operator()(const binary_operator& b) { - if (std::holds_alternative>(b.lhs)) { + if (std::holds_alternative(b.lhs)) { multi.push_back(b); } if (auto s = std::get_if(&b.lhs)) { @@ -719,12 +719,12 @@ struct multi_column_range_accumulator { void operator()(const binary_operator& binop) { if (is_compare(binop.op)) { auto opt_values = dynamic_pointer_cast(binop.rhs->bind(options))->get_elements(); - auto& lhs = std::get>(binop.lhs); - std::vector values(lhs.size()); - for (size_t i = 0; i < lhs.size(); ++i) { + auto& lhs = std::get(binop.lhs); + std::vector values(lhs.elements.size()); + for (size_t i = 0; i < lhs.elements.size(); ++i) { values[i] = *statements::request_validations::check_not_null( opt_values[i], - "Invalid null value in condition for column %s", lhs.at(i).col->name_as_text()); + "Invalid null value in condition for column %s", lhs.elements.at(i).col->name_as_text()); } intersect_all(to_range(binop.op, clustering_key_prefix(std::move(values)))); } else if (binop.op == oper_t::IN) { @@ -1029,7 +1029,7 @@ std::vector statement_restrictions::get_clustering_boun if (is_clustering_order(binop)) { return {range_from_raw_bounds(_clustering_prefix_restrictions, options, *_schema)}; } - for (auto& cv : std::get>(binop.lhs)) { + for (auto& cv : std::get(binop.lhs).elements) { if (cv.col->type->is_reversed()) { all_natural = false; } else {