expr: give a name to a tuple of columns
Right now, binary_operator::lhs is a variant<column_value, std::vector<column_value>, token>. The role of the second branch (a vector of column values) is to represent a tuple of columns e.g. "WHERE (a, b, c) = ?"), but this is not clear from the type name. Inroduce a wrapper type around the vector, column_value_tuple, to make it clear we're dealing with tuples of CQL references (a column_value is really a column_ref, since it doesn't actually contain any value). Closes #8208
This commit is contained in:
@@ -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<column_value>& 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<column_value>& 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<column_value>& 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<column_value>& 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<lists::delayed_value*>(&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<term>& t) {
|
||||
return equal(*t, cvs, bag);
|
||||
return equal(*t, tuple, bag);
|
||||
});
|
||||
} else if (auto mkr = dynamic_cast<tuples::in_marker*>(&rhs)) {
|
||||
// This is `(a,b) IN ?`. RHS elements are themselves tuples, represented as vector<managed_bytes_opt>.
|
||||
const auto marker_value = static_pointer_cast<tuples::in_value>(mkr->bind(bag.options));
|
||||
return boost::algorithm::any_of(marker_value->get_split_values(), [&] (const std::vector<managed_bytes_opt>& 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<column_value>& 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<column_value>& 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<column_value>& 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<column_value>& 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<std::vector<column_value>>(&b.lhs)) {
|
||||
return boost::algorithm::any_of(*cvs, [] (const column_value& v) { return v.sub; });
|
||||
if (auto tuple = std::get_if<column_value_tuple>(&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<column_value>& 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; },
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <ostream>
|
||||
#include <seastar/core/shared_ptr.hh>
|
||||
#include <variant>
|
||||
#include <concepts>
|
||||
|
||||
#include "bytes.hh"
|
||||
#include "cql3/query_options.hh"
|
||||
@@ -67,6 +68,19 @@ struct column_value {
|
||||
column_value(const column_definition* col, ::shared_ptr<term> sub) : col(col), sub(sub) {}
|
||||
};
|
||||
|
||||
/// A tuple of column selectors. Usually used for range constraints on clustering keys.
|
||||
struct column_value_tuple {
|
||||
std::vector<column_value> elements;
|
||||
explicit column_value_tuple(std::vector<column_value>);
|
||||
template <typename Range>
|
||||
requires requires (Range r) {
|
||||
{ r.begin() } -> std::input_iterator;
|
||||
{ r.end() } -> std::input_iterator;
|
||||
{ *r.begin() } -> std::convertible_to<column_value>;
|
||||
}
|
||||
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<column_value, std::vector<column_value>, token> lhs;
|
||||
std::variant<column_value, column_value_tuple, token> lhs;
|
||||
oper_t op;
|
||||
::shared_ptr<term> 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<std::vector<column_value>>(op.lhs);
|
||||
return holds_alternative<column_value_tuple>(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<column_value> e)
|
||||
: elements(std::move(e)) {
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
requires requires (Range r) {
|
||||
{ r.begin() } -> std::input_iterator;
|
||||
{ r.end() } -> std::input_iterator;
|
||||
{ *r.begin() } -> std::convertible_to<column_value>;
|
||||
}
|
||||
column_value_tuple::column_value_tuple(Range r)
|
||||
: column_value_tuple(std::vector<column_value>(r.begin(), r.end())) {
|
||||
}
|
||||
|
||||
|
||||
} // namespace expr
|
||||
|
||||
} // namespace cql3
|
||||
|
||||
@@ -190,7 +190,7 @@ public:
|
||||
{
|
||||
using namespace expr;
|
||||
expression = binary_operator{
|
||||
std::vector<column_value>(_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_value>(_column_defs.cbegin(), _column_defs.cend()),
|
||||
column_value_tuple(_column_defs),
|
||||
oper_t::IN,
|
||||
::make_shared<lists::delayed_value>(_values)};
|
||||
}
|
||||
@@ -361,7 +361,7 @@ public:
|
||||
: IN(schema, std::move(defs)), _marker(marker) {
|
||||
using namespace expr;
|
||||
expression = binary_operator{
|
||||
std::vector<column_value>(_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<expr::column_value>(defs.cbegin(), defs.cend()),
|
||||
expr::column_value_tuple(defs),
|
||||
expr::pick_operator(bound, inclusive),
|
||||
std::move(term),
|
||||
m};
|
||||
|
||||
@@ -171,7 +171,7 @@ public:
|
||||
|
||||
virtual void merge_with(::shared_ptr<restriction> restriction) override {
|
||||
if (find_atom(restriction->expression, [] (const expr::binary_operator& b) {
|
||||
return std::holds_alternative<std::vector<expr::column_value>>(b.lhs);
|
||||
return std::holds_alternative<expr::column_value_tuple>(b.lhs);
|
||||
})) {
|
||||
throw exceptions::invalid_request_exception(
|
||||
"Mixing single column relations and multi column relations on clustering columns is not allowed");
|
||||
|
||||
@@ -173,7 +173,7 @@ static std::vector<expr::expression> extract_clustering_prefix_restrictions(
|
||||
}
|
||||
|
||||
void operator()(const binary_operator& b) {
|
||||
if (std::holds_alternative<std::vector<column_value>>(b.lhs)) {
|
||||
if (std::holds_alternative<column_value_tuple>(b.lhs)) {
|
||||
multi.push_back(b);
|
||||
}
|
||||
if (auto s = std::get_if<column_value>(&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<tuples::value>(binop.rhs->bind(options))->get_elements();
|
||||
auto& lhs = std::get<std::vector<column_value>>(binop.lhs);
|
||||
std::vector<managed_bytes> values(lhs.size());
|
||||
for (size_t i = 0; i < lhs.size(); ++i) {
|
||||
auto& lhs = std::get<column_value_tuple>(binop.lhs);
|
||||
std::vector<managed_bytes> 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<query::clustering_range> 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<std::vector<column_value>>(binop.lhs)) {
|
||||
for (auto& cv : std::get<column_value_tuple>(binop.lhs).elements) {
|
||||
if (cv.col->type->is_reversed()) {
|
||||
all_natural = false;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user