Files
scylladb/cql3/multi_column_relation.hh
Avi Kivity fcb8d040e8 treewide: use Software Package Data Exchange (SPDX) license identifiers
Instead of lengthy blurbs, switch to single-line, machine-readable
standardized (https://spdx.dev) license identifiers. The Linux kernel
switched long ago, so there is strong precedent.

Three cases are handled: AGPL-only, Apache-only, and dual licensed.
For the latter case, I chose (AGPL-3.0-or-later and Apache-2.0),
reasoning that our changes are extensive enough to apply our license.

The changes we applied mechanically with a script, except to
licenses/README.md.

Closes #9937
2022-01-18 12:15:18 +01:00

234 lines
12 KiB
C++

/*
*/
/*
* Copyright (C) 2015-present ScyllaDB
*
* Modified by ScyllaDB
*/
/*
* SPDX-License-Identifier: (AGPL-3.0-or-later and Apache-2.0)
*/
#pragma once
#include "cql3/expr/expression.hh"
#include "cql3/relation.hh"
#include "cql3/restrictions/multi_column_restriction.hh"
#include <ranges>
namespace cql3 {
/**
* A relation using the tuple notation, which typically affects multiple columns.
* Examples:
* - SELECT ... WHERE (a, b, c) > (1, 'a', 10)
* - SELECT ... WHERE (a, b, c) IN ((1, 2, 3), (4, 5, 6))
* - SELECT ... WHERE (a, b) < ?
* - SELECT ... WHERE (a, b) IN ?
*/
class multi_column_relation final : public relation {
public:
using mode = expr::comparison_order;
private:
std::vector<shared_ptr<column_identifier::raw>> _entities;
std::optional<expr::expression> _values_or_marker;
std::vector<expr::expression> _in_values;
std::optional<expr::expression> _in_marker;
mode _mode;
public:
multi_column_relation(std::vector<shared_ptr<column_identifier::raw>> entities,
expr::oper_t relation_type, std::optional<expr::expression> values_or_marker,
std::vector<expr::expression> in_values, std::optional<expr::expression> in_marker, mode m = mode::cql)
: relation(relation_type)
, _entities(std::move(entities))
, _values_or_marker(std::move(values_or_marker))
, _in_values(std::move(in_values))
, _in_marker(std::move(in_marker))
, _mode(m)
{ }
static shared_ptr<multi_column_relation> create_multi_column_relation(
std::vector<shared_ptr<column_identifier::raw>> entities, expr::oper_t relation_type,
std::optional<expr::expression> values_or_marker, std::vector<expr::expression> in_values,
std::optional<expr::expression> in_marker, mode m = mode::cql) {
return ::make_shared<multi_column_relation>(std::move(entities), relation_type, std::move(values_or_marker),
std::move(in_values), std::move(in_marker), m);
}
/**
* Creates a multi-column EQ, LT, LTE, GT, or GTE relation.
* For example: "SELECT ... WHERE (a, b) > (0, 1)"
* @param entities the columns on the LHS of the relation
* @param relationType the relation operator
* @param valuesOrMarker a Tuples.Literal instance or a Tuples.Raw marker
* @return a new <code>MultiColumnRelation</code> instance
*/
static shared_ptr<multi_column_relation> create_non_in_relation(std::vector<shared_ptr<column_identifier::raw>> entities,
expr::oper_t relation_type, expr::expression values_or_marker) {
assert(relation_type != expr::oper_t::IN);
return create_multi_column_relation(std::move(entities), relation_type, std::move(values_or_marker), {}, {});
}
/**
* Same as above, but sets the magic mode that causes us to treat the restrictions as "raw" clustering bounds
*/
static shared_ptr<multi_column_relation> create_scylla_clustering_bound_non_in_relation(std::vector<shared_ptr<column_identifier::raw>> entities,
expr::oper_t relation_type, expr::expression values_or_marker) {
assert(relation_type != expr::oper_t::IN);
return create_multi_column_relation(std::move(entities), relation_type, std::move(values_or_marker), {}, {}, mode::clustering);
}
/**
* Creates a multi-column IN relation with a list of IN values or markers.
* For example: "SELECT ... WHERE (a, b) IN ((0, 1), (2, 3))"
* @param entities the columns on the LHS of the relation
* @param inValues a list of Tuples.Literal instances or a Tuples.Raw markers
* @return a new <code>MultiColumnRelation</code> instance
*/
static shared_ptr<multi_column_relation> create_in_relation(std::vector<shared_ptr<column_identifier::raw>> entities,
std::vector<expr::expression> values) {
return create_multi_column_relation(std::move(entities), expr::oper_t::IN, {}, std::move(values), {});
}
/**
* Creates a multi-column IN relation with a marker for the IN values.
* For example: "SELECT ... WHERE (a, b) IN ?"
* @param entities the columns on the LHS of the relation
* @param inMarker a single IN marker
* @return a new <code>MultiColumnRelation</code> instance
*/
static shared_ptr<multi_column_relation> create_single_marker_in_relation(std::vector<shared_ptr<column_identifier::raw>> entities,
expr::expression in_marker) {
return create_multi_column_relation(std::move(entities), expr::oper_t::IN, {}, {}, std::move(in_marker));
}
const std::vector<shared_ptr<column_identifier::raw>>& get_entities() const {
return _entities;
}
private:
/**
* For non-IN relations, returns the Tuples.Literal or Tuples.Raw marker for a single tuple.
* @return a Tuples.Literal for non-IN relations or Tuples.Raw marker for a single tuple.
*/
const expr::expression& get_value() {
return _relation_type == expr::oper_t::IN ? *_in_marker : *_values_or_marker;
}
public:
virtual bool is_multi_column() const override { return true; }
protected:
virtual shared_ptr<restrictions::restriction> new_EQ_restriction(data_dictionary::database db, schema_ptr schema,
prepare_context& ctx) override {
auto rs = receivers(db, *schema);
std::vector<lw_shared_ptr<column_specification>> col_specs(rs.size());
std::transform(rs.begin(), rs.end(), col_specs.begin(), [] (auto cs) {
return cs->column_specification;
});
auto e = to_expression(col_specs, get_value(), db, schema->ks_name(), ctx);
return ::make_shared<restrictions::multi_column_restriction::EQ>(schema, rs, std::move(e));
}
virtual shared_ptr<restrictions::restriction> new_IN_restriction(data_dictionary::database db, schema_ptr schema,
prepare_context& ctx) override {
auto rs = receivers(db, *schema);
std::vector<lw_shared_ptr<column_specification>> col_specs(rs.size());
std::transform(rs.begin(), rs.end(), col_specs.begin(), [] (auto cs) {
return cs->column_specification;
});
if (_in_marker) {
auto e = to_expression(col_specs, get_value(), db, schema->ks_name(), ctx);
auto bound_value_marker = expr::as<expr::bind_variable>(e);
return ::make_shared<restrictions::multi_column_restriction::IN_with_marker>(schema, rs, std::move(bound_value_marker));
} else {
std::vector<expr::expression> raws(_in_values.size());
std::copy(_in_values.begin(), _in_values.end(), raws.begin());
auto es = to_expressions(col_specs, raws, db, schema->ks_name(), ctx);
// Convert a single-item IN restriction to an EQ restriction
if (es.size() == 1) {
return ::make_shared<restrictions::multi_column_restriction::EQ>(schema, rs, std::move(es[0]));
}
return ::make_shared<restrictions::multi_column_restriction::IN_with_values>(schema, rs, std::move(es));
}
}
virtual shared_ptr<restrictions::restriction> new_slice_restriction(data_dictionary::database db, schema_ptr schema,
prepare_context& ctx,
statements::bound bound, bool inclusive) override {
auto rs = receivers(db, *schema);
std::vector<lw_shared_ptr<column_specification>> col_specs(rs.size());
std::transform(rs.begin(), rs.end(), col_specs.begin(), [] (auto cs) {
return cs->column_specification;
});
auto e = to_expression(col_specs, get_value(), db, schema->ks_name(), ctx);
return ::make_shared<restrictions::multi_column_restriction::slice>(schema, rs, bound, inclusive, std::move(e), _mode);
}
virtual shared_ptr<restrictions::restriction> new_contains_restriction(data_dictionary::database db, schema_ptr schema,
prepare_context& ctx, bool is_key) override {
throw exceptions::invalid_request_exception(format("{} cannot be used for Multi-column relations", get_operator()));
}
virtual ::shared_ptr<restrictions::restriction> new_LIKE_restriction(
data_dictionary::database db, schema_ptr schema, prepare_context& ctx) override {
throw exceptions::invalid_request_exception("LIKE cannot be used for Multi-column relations");
}
virtual ::shared_ptr<relation> maybe_rename_identifier(const column_identifier::raw& from, column_identifier::raw to) override {
auto new_entities = boost::copy_range<decltype(_entities)>(_entities | boost::adaptors::transformed([&] (auto&& entity) {
return *entity == from ? ::make_shared<column_identifier::raw>(to) : entity;
}));
return create_multi_column_relation(std::move(new_entities), _relation_type, _values_or_marker, _in_values, _in_marker);
}
virtual expr::expression to_expression(const std::vector<lw_shared_ptr<column_specification>>& receivers,
const expr::expression& raw, data_dictionary::database db, const sstring& keyspace,
prepare_context& ctx) const override {
auto e = prepare_expression_multi_column(raw, db, keyspace, receivers);
expr::fill_prepare_context(e, ctx);
return e;
}
std::vector<const column_definition*> receivers(data_dictionary::database db, const schema& schema) {
using namespace statements::request_validations;
int previous_position = -1;
std::vector<const column_definition*> names;
for (auto&& raw : get_entities()) {
const auto& def = to_column_definition(schema, *raw);
check_true(def.is_clustering_key(), "Multi-column relations can only be applied to clustering columns but was applied to: {}", def.name_as_text());
check_false(std::count(names.begin(), names.end(), &def), "Column \"{}\" appeared twice in a relation: {}", def.name_as_text(), to_string());
// FIXME: the following restriction should be removed (CASSANDRA-8613)
if (def.position() != unsigned(previous_position + 1)) {
check_false(previous_position == -1, "Clustering columns may not be skipped in multi-column relations. "
"They should appear in the PRIMARY KEY order. Got {}", to_string());
throw exceptions::invalid_request_exception(format("Clustering columns must appear in the PRIMARY KEY order in multi-column relations: {}", to_string()));
}
names.emplace_back(&def);
previous_position = def.position();
}
return names;
}
template <typename T>
static sstring tuple_to_string(const std::vector<T>& items) {
return format("({})", join(", ", items));
}
virtual sstring to_string() const override {
sstring str = tuple_to_string(_entities);
if (is_IN()) {
str += " IN ";
str += !_in_marker ? "?" : tuple_to_string(_in_values);
return str;
}
return format("{} {} {}", str, _relation_type, *_values_or_marker);
}
};
}