Files
scylladb/cql3/selection/selectable.cc
Avi Kivity 7a798b44a2 cql3: expr: replace column_value_tuple by a composition of tuple_constructor and column_value
column_value_tuple overlaps both column_value and tuple_constructor
(in different respects) and can be replaced by a combination: a
tuple_constructor of column_value. The replacement is more expressive
(we can have a tuple of column_value and other expression types), though
the code (especially grammar) do not allow it yet.

So remove column_value_tuple and replace it everywhere with
tuple_constructor. Visitors get the merged behavior of the existing
tuple_constructor and column_value_tuple, which is usually trivial
since tuple_constructor and column_value_tuple came from different
hierarchies (term::raw and relation), so usually one of the types
just calls on_internal_error().

The change results in awkwards casts in two areas: WHERE clause
filtering (equal() and related), and clustering key range evaluations
(limits() and related). When equal() is replaced by recursive
evaluate(), the casts will go way (to be replaced by the evaluate())
visitor. Clustering key range extraction will remain limited
to tuples of column_value, so the prepare phase will have to vet
the expressions to ensure the casts don't fail (and use the
filtering path if they will).

Tests: unit (dev)

Closes #9274
2021-09-10 10:43:29 +02:00

294 lines
13 KiB
C++

/*
* Copyright (C) 2015-present ScyllaDB
*/
/*
* This file is part of Scylla.
*
* Scylla is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Scylla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
*/
#include "selectable.hh"
#include "selectable_with_field_selection.hh"
#include "field_selector.hh"
#include "writetime_or_ttl.hh"
#include "selector_factories.hh"
#include "cql3/query_options.hh"
#include "cql3/functions/functions.hh"
#include "cql3/functions/castas_fcts.hh"
#include "cql3/functions/aggregate_fcts.hh"
#include "abstract_function_selector.hh"
#include "writetime_or_ttl_selector.hh"
namespace cql3 {
namespace selection {
seastar::logger slogger("cql3_selection");
shared_ptr<selector::factory>
selectable::writetime_or_ttl::new_selector_factory(database& db, schema_ptr s, std::vector<const column_definition*>& defs) {
auto&& def = s->get_column_definition(_id->name());
if (!def || def->is_hidden_from_cql()) {
throw exceptions::invalid_request_exception(format("Undefined name {} in selection clause", _id));
}
if (def->is_primary_key()) {
throw exceptions::invalid_request_exception(
format("Cannot use selection function {} on PRIMARY KEY part {}",
_is_writetime ? "writeTime" : "ttl",
def->name()));
}
if (def->type->is_multi_cell()) {
throw exceptions::invalid_request_exception(format("Cannot use selection function {} on non-frozen collections",
_is_writetime ? "writeTime" : "ttl"));
}
return writetime_or_ttl_selector::new_factory(def->name_as_text(), add_and_get_index(*def, defs), _is_writetime);
}
sstring
selectable::writetime_or_ttl::to_string() const {
return format("{}({})", _is_writetime ? "writetime" : "ttl", _id->to_string());
}
shared_ptr<selector::factory>
selectable::with_function::new_selector_factory(database& db, schema_ptr s, std::vector<const column_definition*>& defs) {
auto&& factories = selector_factories::create_factories_and_collect_column_definitions(_args, db, s, defs);
// resolve built-in functions before user defined functions
auto&& fun = functions::functions::get(db, s->ks_name(), _function_name, factories->new_instances(), s->ks_name(), s->cf_name());
if (!fun) {
throw exceptions::invalid_request_exception(format("Unknown function '{}'", _function_name));
}
if (!fun->return_type()) {
throw exceptions::invalid_request_exception(format("Unknown function {} called in selection clause", _function_name));
}
return abstract_function_selector::new_factory(std::move(fun), std::move(factories));
}
sstring
selectable::with_function::to_string() const {
return format("{}({})", _function_name.name, join(", ", _args));
}
expr::expression
make_count_rows_function_expression() {
return expr::function_call{
cql3::functions::function_name::native_function(cql3::functions::aggregate_fcts::COUNT_ROWS_FUNCTION_NAME),
std::vector<cql3::expr::expression>()};
}
shared_ptr<selector::factory>
selectable::with_anonymous_function::new_selector_factory(database& db, schema_ptr s, std::vector<const column_definition*>& defs) {
auto&& factories = selector_factories::create_factories_and_collect_column_definitions(_args, db, s, defs);
return abstract_function_selector::new_factory(_function, std::move(factories));
}
sstring
selectable::with_anonymous_function::to_string() const {
return format("{}({})", _function->name().name, join(", ", _args));
}
shared_ptr<selector::factory>
selectable::with_field_selection::new_selector_factory(database& db, schema_ptr s, std::vector<const column_definition*>& defs) {
auto&& factory = _selected->new_selector_factory(db, s, defs);
auto&& type = factory->new_instance()->get_type();
if (!type->underlying_type()->is_user_type()) {
throw exceptions::invalid_request_exception(
format("Invalid field selection: {} of type {} is not a user type", _selected->to_string(), type->as_cql3_type()));
}
auto ut = static_pointer_cast<const user_type_impl>(type->underlying_type());
auto idx = ut->idx_of_field(_field->bytes_);
if (!idx) {
throw exceptions::invalid_request_exception(format("{} of type {} has no field {}",
_selected->to_string(), ut->as_cql3_type(), _field));
}
return field_selector::new_factory(std::move(ut), *idx, std::move(factory));
}
sstring
selectable::with_field_selection::to_string() const {
return format("{}.{}", _selected->to_string(), _field->to_string());
}
shared_ptr<selector::factory>
selectable::with_cast::new_selector_factory(database& db, schema_ptr s, std::vector<const column_definition*>& defs) {
std::vector<shared_ptr<selectable>> args{_arg};
auto&& factories = selector_factories::create_factories_and_collect_column_definitions(args, db, s, defs);
auto&& fun = functions::castas_functions::get(_type.get_type(), factories->new_instances());
return abstract_function_selector::new_factory(std::move(fun), std::move(factories));
}
sstring
selectable::with_cast::to_string() const {
return format("cast({} as {})", _arg->to_string(), _type.to_string());
}
shared_ptr<selectable>
prepare_selectable(const schema& s, const expr::expression& raw_selectable) {
return std::visit(overloaded_functor{
[&] (bool bool_constant) -> shared_ptr<selectable> {
on_internal_error(slogger, "no way to express SELECT TRUE/FALSE in the grammar yet");
},
[&] (const expr::conjunction& conj) -> shared_ptr<selectable> {
on_internal_error(slogger, "no way to express 'SELECT a AND b' in the grammar yet");
},
[&] (const expr::binary_operator& conj) -> shared_ptr<selectable> {
on_internal_error(slogger, "no way to express 'SELECT a binop b' in the grammar yet");
},
[&] (const expr::column_value& column) -> shared_ptr<selectable> {
// There is no path that reaches here, but expr::column_value and column_identifier are logically the same,
// so bridge them.
return ::make_shared<column_identifier>(column.col->name(), column.col->name_as_text());
},
[&] (const expr::token& tok) -> shared_ptr<selectable> {
// expr::token implicitly the partition key as arguments, but
// the selectable equivalent (with_function) needs explicit arguments,
// so construct them here.
auto name = functions::function_name("system", "token");
auto args = boost::copy_range<std::vector<shared_ptr<selectable>>>(
s.partition_key_columns()
| boost::adaptors::transformed([&] (const column_definition& cdef) {
return ::make_shared<column_identifier>(cdef.name(), cdef.name_as_text());
}));
return ::make_shared<selectable::with_function>(std::move(name), std::move(args));
},
[&] (const expr::unresolved_identifier& ui) -> shared_ptr<selectable> {
return ui.ident->prepare(s);
},
[&] (const expr::column_mutation_attribute& cma) -> shared_ptr<selectable> {
auto unresolved_id = std::get<expr::unresolved_identifier>(*cma.column);
bool is_writetime = cma.kind == expr::column_mutation_attribute::attribute_kind::writetime;
return make_shared<selectable::writetime_or_ttl>(unresolved_id.ident->prepare_column_identifier(s), is_writetime);
},
[&] (const expr::function_call& fc) -> shared_ptr<selectable> {
std::vector<shared_ptr<selectable>> prepared_args;
prepared_args.reserve(fc.args.size());
for (auto&& arg : fc.args) {
prepared_args.push_back(prepare_selectable(s, arg));
}
return std::visit(overloaded_functor{
[&] (const functions::function_name& named) -> shared_ptr<selectable> {
return ::make_shared<selectable::with_function>(named, std::move(prepared_args));
},
[&] (const shared_ptr<functions::function>& anon) -> shared_ptr<selectable> {
return ::make_shared<selectable::with_anonymous_function>(anon, std::move(prepared_args));
},
}, fc.func);
},
[&] (const expr::cast& c) -> shared_ptr<selectable> {
auto t = std::get_if<cql3_type>(&c.type);
if (!t) {
// FIXME: adjust prepare_seletable() signature so we can prepare the type too
on_internal_error(slogger, "unprepared type in selector type cast");
}
return ::make_shared<selectable::with_cast>(prepare_selectable(s, *c.arg), *t);
},
[&] (const expr::field_selection& fs) -> shared_ptr<selectable> {
// static_pointer_cast<> needed due to lack of covariant return type
// support with smart pointers
return make_shared<selectable::with_field_selection>(prepare_selectable(s, *fs.structure),
static_pointer_cast<column_identifier>(fs.field->prepare(s)));
},
[&] (const expr::null&) -> shared_ptr<selectable> {
on_internal_error(slogger, "null found its way to selector context");
},
[&] (const expr::bind_variable&) -> shared_ptr<selectable> {
on_internal_error(slogger, "bind_variable found its way to selector context");
},
[&] (const expr::untyped_constant&) -> shared_ptr<selectable> {
on_internal_error(slogger, "untyped_constant found its way to selector context");
},
[&] (const expr::tuple_constructor&) -> shared_ptr<selectable> {
on_internal_error(slogger, "tuple_constructor found its way to selector context");
},
[&] (const expr::collection_constructor&) -> shared_ptr<selectable> {
on_internal_error(slogger, "collection_constructor found its way to selector context");
},
[&] (const expr::usertype_constructor&) -> shared_ptr<selectable> {
on_internal_error(slogger, "usertype_constructor found its way to selector context");
},
}, raw_selectable);
}
bool
selectable_processes_selection(const expr::expression& raw_selectable) {
return std::visit(overloaded_functor{
[&] (bool bool_constant) -> bool {
on_internal_error(slogger, "no way to express SELECT TRUE/FALSE in the grammar yet");
},
[&] (const expr::conjunction& conj) -> bool {
on_internal_error(slogger, "no way to express 'SELECT a AND b' in the grammar yet");
},
[&] (const expr::binary_operator& conj) -> bool {
on_internal_error(slogger, "no way to express 'SELECT a binop b' in the grammar yet");
},
[&] (const expr::column_value& column) -> bool {
// There is no path that reaches here, but expr::column_value and column_identifier are logically the same,
// so bridge them.
return false;
},
[&] (const expr::token&) -> bool {
// Arguably, should return false, because it only processes the partition key.
// But selectable::with_function considers it true now, so return that.
return true;
},
[&] (const expr::unresolved_identifier& ui) -> bool {
return ui.ident->processes_selection();
},
[&] (const expr::column_mutation_attribute& cma) -> bool {
return true;
},
[&] (const expr::function_call& fc) -> bool {
return true;
},
[&] (const expr::cast& c) -> bool {
return true;
},
[&] (const expr::field_selection& fs) -> bool {
return true;
},
[&] (const expr::null&) -> bool {
on_internal_error(slogger, "null found its way to selector context");
},
[&] (const expr::bind_variable&) -> bool {
on_internal_error(slogger, "bind_variable found its way to selector context");
},
[&] (const expr::untyped_constant&) -> bool {
on_internal_error(slogger, "untyped_constant found its way to selector context");
},
[&] (const expr::tuple_constructor&) -> bool {
on_internal_error(slogger, "tuple_constructor found its way to selector context");
},
[&] (const expr::collection_constructor&) -> bool {
on_internal_error(slogger, "collection_constructor found its way to selector context");
},
[&] (const expr::usertype_constructor&) -> bool {
on_internal_error(slogger, "collection_constructor found its way to selector context");
},
}, raw_selectable);
};
std::ostream & operator<<(std::ostream &os, const selectable& s) {
return os << s.to_string();
}
}
}