mirror of
https://github.com/scylladb/scylladb.git
synced 2026-05-12 19:02:12 +00:00
cql3: remove expr::token
Let's remove expr::token and replace all of its functionality with expr::function_call. expr::token is a struct whose job is to represent a partition key token. The idea is that when the user types in `token(p1, p2) < 1234`, this will be internally represented as an expression which uses expr::token to represent the `token(p1, p2)` part. The situation with expr::token is a bit complicated. On one hand side it's supposed to represent the partition token, but sometimes it's also assumed that it can represent a generic call to the token() function, for example `token(1, 2, 3)` could be a function_call, but it could also be expr::token. The query planning code assumes that each occurence of expr::token represents the partition token without checking the arguments. Because of this allowing `token(1, 2, 3)` to be represented as expr::token is dangerous - the query planning might think that it is `token(p1, p2, p3)` and plan the query based on this, which would be wrong. Currently expr::token is created only in one specific case. When the parser detects that the user typed in a restriction which has a call to `token` on the LHS it generates expr::token. In all other cases it generates an `expr::function_call`. Even when the `function_call` represents a valid partition token, it stays a `function_call`. During preparation there is no check to see if a `function_call` to `token` could be turned into `expr::token`. This is a bit inconsistent - sometimes `token(p1, p2, p3)` is represented as `expr::token` and the query planner handles that, but sometimes it might be represented as `function_call`, which the query planner doesn't handle. There is also a problem because there's a lot of duplication between a `function_call` and `expr::token`. All of the evaluation and preparation is the same for `expr::token` as it's for a `function_call` to the token function. Currently it's impossible to evaluate `expr::token` and preparation has some flaws, but implementing it would basically consist of copy-pasting the corresponding code from token `function_call`. One more aspect is multi-table queries. With `expr::token` we turn a call to the `token()` function into a struct that is schema-specific. What happens when a single expression is used to make queries to multiple tables? The schema is different, so something that is representad as `expr::token` for one schema would be represented as `function_call` in the context of a different schema. Translating expressions to different tables would require careful manipulation to convert `expr::token` to `function_call` and vice versa. This could cause trouble for index queries. Overall I think it would be best to remove expr::token. Although having a clear marker for the partition token is sometimes nice for query planning, in my opinion the pros are outweighted by the cons. I'm a big fan of having a single way to represent things, having two separate representations of the same thing without clear boundaries between them causes trouble. Instead of having expr::token and function_call we can just have the function_call and check if it represents a partition token when needed. Signed-off-by: Jan Ciolek <jan.ciolek@scylladb.com>
This commit is contained in:
@@ -1773,7 +1773,12 @@ relation returns [expression e]
|
||||
: name=cident type=relationType t=term { $e = binary_operator(unresolved_identifier{std::move(name)}, type, std::move(t)); }
|
||||
|
||||
| K_TOKEN l=tupleOfIdentifiers type=relationType t=term
|
||||
{ $e = binary_operator(token{std::move(l.elements)}, type, std::move(t)); }
|
||||
{
|
||||
$e = binary_operator(
|
||||
function_call{functions::function_name::native_function("token"), std::move(l.elements)},
|
||||
type,
|
||||
std::move(t));
|
||||
}
|
||||
| name=cident K_IS K_NOT K_NULL {
|
||||
$e = binary_operator(unresolved_identifier{std::move(name)}, oper_t::IS_NOT, make_untyped_null()); }
|
||||
| name=cident K_IN marker1=marker
|
||||
|
||||
@@ -66,24 +66,6 @@ expression::operator=(const expression& o) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
token::token(std::vector<expression> args_in)
|
||||
: args(std::move(args_in)) {
|
||||
}
|
||||
|
||||
token::token(const std::vector<const column_definition*>& col_defs) {
|
||||
args.reserve(col_defs.size());
|
||||
for (const column_definition* col_def : col_defs) {
|
||||
args.push_back(column_value(col_def));
|
||||
}
|
||||
}
|
||||
|
||||
token::token(const std::vector<::shared_ptr<column_identifier_raw>>& cols) {
|
||||
args.reserve(cols.size());
|
||||
for(const ::shared_ptr<column_identifier_raw>& col : cols) {
|
||||
args.push_back(unresolved_identifier{col});
|
||||
}
|
||||
}
|
||||
|
||||
binary_operator::binary_operator(expression lhs, oper_t op, expression rhs, comparison_order order)
|
||||
: lhs(std::move(lhs))
|
||||
, op(op)
|
||||
@@ -789,7 +771,11 @@ static value_set possible_lhs_values(const column_definition* cdef,
|
||||
}
|
||||
return unbounded_value_set;
|
||||
},
|
||||
[&] (token) -> value_set {
|
||||
[&] (const function_call& token_fun_call) -> value_set {
|
||||
if (!is_partition_token_for_schema(token_fun_call, *table_schema_opt)) {
|
||||
on_internal_error(expr_logger, "possible_lhs_values: function calls are not supported as the LHS of a binary expression");
|
||||
}
|
||||
|
||||
if (cdef) {
|
||||
return unbounded_value_set;
|
||||
}
|
||||
@@ -831,9 +817,6 @@ static value_set possible_lhs_values(const column_definition* cdef,
|
||||
[] (const column_mutation_attribute&) -> value_set {
|
||||
on_internal_error(expr_logger, "possible_lhs_values: writetime/ttl are not supported as the LHS of a binary expression");
|
||||
},
|
||||
[] (const function_call&) -> value_set {
|
||||
on_internal_error(expr_logger, "possible_lhs_values: function calls are not supported as the LHS of a binary expression");
|
||||
},
|
||||
[] (const cast&) -> value_set {
|
||||
on_internal_error(expr_logger, "possible_lhs_values: typecasts are not supported as the LHS of a binary expression");
|
||||
},
|
||||
@@ -860,9 +843,6 @@ static value_set possible_lhs_values(const column_definition* cdef,
|
||||
[] (const subscript&) -> value_set {
|
||||
on_internal_error(expr_logger, "possible_lhs_values: a subscript cannot serve as a restriction by itself");
|
||||
},
|
||||
[] (const token&) -> value_set {
|
||||
on_internal_error(expr_logger, "possible_lhs_values: the token function cannot serve as a restriction by itself");
|
||||
},
|
||||
[] (const unresolved_identifier&) -> value_set {
|
||||
on_internal_error(expr_logger, "possible_lhs_values: an unresolved identifier cannot serve as a restriction");
|
||||
},
|
||||
@@ -951,7 +931,7 @@ secondary_index::index::supports_expression_v is_supported_by_helper(const expre
|
||||
// We don't use index table for multi-column restrictions, as it cannot avoid filtering.
|
||||
return index::supports_expression_v::from_bool(false);
|
||||
},
|
||||
[&] (const token&) { return index::supports_expression_v::from_bool(false); },
|
||||
[&] (const function_call&) { return index::supports_expression_v::from_bool(false); },
|
||||
[&] (const subscript& s) -> ret_t {
|
||||
const column_value& col = get_subscripted_column(s);
|
||||
return idx.supports_subscript_expression(*col.col, oper.op);
|
||||
@@ -971,9 +951,6 @@ secondary_index::index::supports_expression_v is_supported_by_helper(const expre
|
||||
[&] (const column_mutation_attribute&) -> ret_t {
|
||||
on_internal_error(expr_logger, "is_supported_by: writetime/ttl are not supported as the LHS of a binary expression");
|
||||
},
|
||||
[&] (const function_call&) -> ret_t {
|
||||
on_internal_error(expr_logger, "is_supported_by: function calls are not supported as the LHS of a binary expression");
|
||||
},
|
||||
[&] (const cast&) -> ret_t {
|
||||
on_internal_error(expr_logger, "is_supported_by: typecasts are not supported as the LHS of a binary expression");
|
||||
},
|
||||
@@ -1097,9 +1074,6 @@ std::ostream& operator<<(std::ostream& os, const expression::printer& pr) {
|
||||
}
|
||||
}
|
||||
},
|
||||
[&] (const token& t) {
|
||||
fmt::print(os, "token({})", fmt::join(t.args | transformed(to_printer), ", "));
|
||||
},
|
||||
[&] (const column_value& col) {
|
||||
fmt::print(os, "{}", cql3::util::maybe_quote(col.col->name_as_text()));
|
||||
},
|
||||
@@ -1307,7 +1281,7 @@ expression replace_column_def(const expression& expr, const column_definition* n
|
||||
|
||||
expression replace_partition_token(const expression& expr, const column_definition* new_cdef, const schema& table_schema) {
|
||||
return search_and_replace(expr, [&] (const expression& expr) -> std::optional<expression> {
|
||||
if (expr::is<token>(expr)) {
|
||||
if (is_partition_token_for_schema(expr, table_schema)) {
|
||||
return column_value{new_cdef};
|
||||
} else {
|
||||
return std::nullopt;
|
||||
@@ -1381,14 +1355,6 @@ bool recurse_until(const expression& e, const noncopyable_function<bool (const e
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[&] (const token& tok) {
|
||||
for (auto& a : tok.args) {
|
||||
if (auto found = recurse_until(a, predicate_fun)) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[](LeafExpression auto const&) {
|
||||
return false;
|
||||
}
|
||||
@@ -1464,13 +1430,6 @@ expression search_and_replace(const expression& e,
|
||||
.type = s.type,
|
||||
};
|
||||
},
|
||||
[&](const token& tok) -> expression {
|
||||
return token {
|
||||
boost::copy_range<std::vector<expression>>(
|
||||
tok.args | boost::adaptors::transformed(recurse)
|
||||
)
|
||||
};
|
||||
},
|
||||
[&] (LeafExpression auto const& e) -> expression {
|
||||
return e;
|
||||
},
|
||||
@@ -1545,7 +1504,6 @@ std::vector<expression> extract_single_column_restrictions_for_column(const expr
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(const token&) {}
|
||||
void operator()(const unresolved_identifier&) {}
|
||||
void operator()(const column_mutation_attribute&) {}
|
||||
void operator()(const function_call&) {}
|
||||
@@ -1709,9 +1667,6 @@ cql3::raw_value evaluate(const expression& e, const evaluation_inputs& inputs) {
|
||||
[&](const conjunction& conj) -> cql3::raw_value {
|
||||
return evaluate(conj, inputs);
|
||||
},
|
||||
[](const token&) -> cql3::raw_value {
|
||||
on_internal_error(expr_logger, "Can't evaluate token");
|
||||
},
|
||||
[](const unresolved_identifier&) -> cql3::raw_value {
|
||||
on_internal_error(expr_logger, "Can't evaluate unresolved_identifier");
|
||||
},
|
||||
@@ -2251,11 +2206,6 @@ void fill_prepare_context(expression& e, prepare_context& ctx) {
|
||||
fill_prepare_context(child, ctx);
|
||||
}
|
||||
},
|
||||
[&](token& tok) {
|
||||
for (expression& arg : tok.args) {
|
||||
fill_prepare_context(arg, ctx);
|
||||
}
|
||||
},
|
||||
[](unresolved_identifier&) {},
|
||||
[&](column_mutation_attribute& a) {
|
||||
fill_prepare_context(a.column, ctx);
|
||||
@@ -2305,9 +2255,6 @@ type_of(const expression& e) {
|
||||
[] (const column_value& e) {
|
||||
return e.col->type;
|
||||
},
|
||||
[] (const token& e) {
|
||||
return long_type;
|
||||
},
|
||||
[] (const unresolved_identifier& e) -> data_type {
|
||||
on_internal_error(expr_logger, "evaluating type of unresolved_identifier");
|
||||
},
|
||||
|
||||
@@ -70,7 +70,6 @@ struct binary_operator;
|
||||
struct conjunction;
|
||||
struct column_value;
|
||||
struct subscript;
|
||||
struct token;
|
||||
struct unresolved_identifier;
|
||||
struct column_mutation_attribute;
|
||||
struct function_call;
|
||||
@@ -89,7 +88,6 @@ concept ExpressionElement
|
||||
|| std::same_as<T, binary_operator>
|
||||
|| std::same_as<T, column_value>
|
||||
|| std::same_as<T, subscript>
|
||||
|| std::same_as<T, token>
|
||||
|| std::same_as<T, unresolved_identifier>
|
||||
|| std::same_as<T, column_mutation_attribute>
|
||||
|| std::same_as<T, function_call>
|
||||
@@ -109,7 +107,6 @@ concept invocable_on_expression
|
||||
&& std::invocable<Func, binary_operator>
|
||||
&& std::invocable<Func, column_value>
|
||||
&& std::invocable<Func, subscript>
|
||||
&& std::invocable<Func, token>
|
||||
&& std::invocable<Func, unresolved_identifier>
|
||||
&& std::invocable<Func, column_mutation_attribute>
|
||||
&& std::invocable<Func, function_call>
|
||||
@@ -129,7 +126,6 @@ concept invocable_on_expression_ref
|
||||
&& std::invocable<Func, binary_operator&>
|
||||
&& std::invocable<Func, column_value&>
|
||||
&& std::invocable<Func, subscript&>
|
||||
&& std::invocable<Func, token&>
|
||||
&& std::invocable<Func, unresolved_identifier&>
|
||||
&& std::invocable<Func, column_mutation_attribute&>
|
||||
&& std::invocable<Func, function_call&>
|
||||
@@ -229,18 +225,6 @@ const column_value& get_subscripted_column(const subscript&);
|
||||
/// Only columns can be subscripted in CQL, so we can expect that the subscripted expression is a column_value.
|
||||
const column_value& get_subscripted_column(const expression&);
|
||||
|
||||
/// Represents token(c1, c2) function on LHS of an operator relation.
|
||||
/// args contains arguments to the token function.
|
||||
struct token {
|
||||
std::vector<expression> args;
|
||||
|
||||
explicit token(std::vector<expression>);
|
||||
explicit token(const std::vector<const column_definition*>&);
|
||||
explicit token(const std::vector<::shared_ptr<column_identifier_raw>>&);
|
||||
|
||||
friend bool operator==(const token&, const token&) = default;
|
||||
};
|
||||
|
||||
enum class oper_t { EQ, NEQ, LT, LTE, GTE, GT, IN, CONTAINS, CONTAINS_KEY, IS_NOT, LIKE };
|
||||
|
||||
/// Describes the nature of clustering-key comparisons. Useful for implementing SCYLLA_CLUSTERING_BOUND.
|
||||
@@ -429,7 +413,7 @@ struct usertype_constructor {
|
||||
// now that all expression types are fully defined, we can define expression::impl
|
||||
struct expression::impl final {
|
||||
using variant_type = std::variant<
|
||||
conjunction, binary_operator, column_value, token, unresolved_identifier,
|
||||
conjunction, binary_operator, column_value, unresolved_identifier,
|
||||
column_mutation_attribute, function_call, cast, field_selection,
|
||||
bind_variable, untyped_constant, constant, tuple_constructor, collection_constructor,
|
||||
usertype_constructor, subscript>;
|
||||
@@ -643,13 +627,21 @@ inline bool is_multi_column(const binary_operator& op) {
|
||||
return expr::is<tuple_constructor>(op.lhs);
|
||||
}
|
||||
|
||||
// Check whether the given expression represents
|
||||
// a call to the token() function.
|
||||
bool is_token_function(const function_call&);
|
||||
bool is_token_function(const expression&);
|
||||
|
||||
bool is_partition_token_for_schema(const function_call&, const schema&);
|
||||
bool is_partition_token_for_schema(const expression&, const schema&);
|
||||
|
||||
/// Check whether the expression contains a binary_operator whose LHS is a call to the token
|
||||
/// function representing a partition key token.
|
||||
/// Examples:
|
||||
/// For expression: "token(p1, p2, p3) < 123 AND c = 2" returns true
|
||||
/// For expression: "p1 = token(1, 2, 3) AND c = 2" return false
|
||||
inline bool has_partition_token(const expression& e, const schema& table_schema) {
|
||||
return find_binop(e, [] (const binary_operator& o) { return expr::is<token>(o.lhs); });
|
||||
return find_binop(e, [&] (const binary_operator& o) { return is_partition_token_for_schema(o.lhs, table_schema); });
|
||||
}
|
||||
|
||||
inline bool has_slice_or_needs_filtering(const expression& e) {
|
||||
@@ -831,14 +823,6 @@ bytes_opt value_for(const column_definition&, const expression&, const query_opt
|
||||
bool contains_multi_column_restriction(const expression&);
|
||||
|
||||
bool has_only_eq_binops(const expression&);
|
||||
|
||||
// Check whether the given expression represents
|
||||
// a call to the token() function.
|
||||
bool is_token_function(const function_call&);
|
||||
bool is_token_function(const expression&);
|
||||
|
||||
bool is_partition_token_for_schema(const function_call&, const schema&);
|
||||
bool is_partition_token_for_schema(const expression&, const schema&);
|
||||
} // namespace expr
|
||||
|
||||
} // namespace cql3
|
||||
|
||||
@@ -1057,24 +1057,6 @@ try_prepare_expression(const expression& expr, data_dictionary::database db, con
|
||||
.type = static_cast<const collection_type_impl&>(sub_col_type).value_comparator(),
|
||||
};
|
||||
},
|
||||
[&] (const token& tk) -> std::optional<expression> {
|
||||
if (!schema_opt) {
|
||||
throw exceptions::invalid_request_exception("cannot process token() function without schema");
|
||||
}
|
||||
|
||||
std::vector<expression> prepared_token_args;
|
||||
prepared_token_args.reserve(tk.args.size());
|
||||
|
||||
for (const expression& arg : tk.args) {
|
||||
auto prepared_arg_opt = try_prepare_expression(arg, db, keyspace, schema_opt, receiver);
|
||||
if (!prepared_arg_opt) {
|
||||
return std::nullopt;
|
||||
}
|
||||
prepared_token_args.emplace_back(std::move(*prepared_arg_opt));
|
||||
}
|
||||
|
||||
return token(std::move(prepared_token_args));
|
||||
},
|
||||
[&] (const unresolved_identifier& unin) -> std::optional<expression> {
|
||||
if (!schema_opt) {
|
||||
throw exceptions::invalid_request_exception(fmt::format("Cannot resolve column {} without schema", unin.ident->to_cql_string()));
|
||||
@@ -1135,9 +1117,6 @@ test_assignment(const expression& expr, data_dictionary::database db, const sstr
|
||||
[&] (const subscript&) -> test_result {
|
||||
on_internal_error(expr_logger, "subscripts are not yet reachable via test_assignment()");
|
||||
},
|
||||
[&] (const token&) -> test_result {
|
||||
on_internal_error(expr_logger, "tokens are not yet reachable via test_assignment()");
|
||||
},
|
||||
[&] (const unresolved_identifier&) -> test_result {
|
||||
on_internal_error(expr_logger, "unresolved_identifiers are not yet reachable via test_assignment()");
|
||||
},
|
||||
@@ -1264,12 +1243,6 @@ static lw_shared_ptr<column_specification> get_lhs_receiver(const expression& pr
|
||||
data_type tuple_type = tuple_type_impl::get_instance(tuple_types);
|
||||
return make_lw_shared<column_specification>(schema.ks_name(), schema.cf_name(), std::move(identifier), std::move(tuple_type));
|
||||
},
|
||||
[&](const token& col_val) -> lw_shared_ptr<column_specification> {
|
||||
return make_lw_shared<column_specification>(schema.ks_name(),
|
||||
schema.cf_name(),
|
||||
::make_shared<column_identifier>("partition key token", true),
|
||||
dht::token::get_token_validator());
|
||||
},
|
||||
[&](const function_call& fun_call) -> lw_shared_ptr<column_specification> {
|
||||
data_type return_type = std::visit(
|
||||
overloaded_functor{
|
||||
|
||||
@@ -152,7 +152,10 @@ void preliminary_binop_vaidation_checks(const binary_operator& binop) {
|
||||
}
|
||||
}
|
||||
|
||||
if (is<token>(binop.lhs)) {
|
||||
// Right now a token() on the LHS means that there's a partition token there.
|
||||
// In the future with relaxed grammar this might no longer be true and this check will have to be revisisted.
|
||||
// Moving the check after preparation would break tests and cassandra compatability.
|
||||
if (is_token_function(binop.lhs)) {
|
||||
if (binop.op == oper_t::IN) {
|
||||
throw exceptions::invalid_request_exception("IN cannot be used with the token function");
|
||||
}
|
||||
@@ -214,9 +217,9 @@ binary_operator validate_and_prepare_new_restriction(const binary_operator& rest
|
||||
}
|
||||
|
||||
validate_multi_column_relation(lhs_cols, prepared_binop.op);
|
||||
} else if (auto lhs_token = as_if<token>(&prepared_binop.lhs)) {
|
||||
} else if (is_token_function(prepared_binop.lhs)) {
|
||||
// Token restriction
|
||||
std::vector<const column_definition*> column_defs = to_column_definitions(lhs_token->args);
|
||||
std::vector<const column_definition*> column_defs = to_column_definitions(as<function_call>(prepared_binop.lhs).args);
|
||||
validate_token_relation(column_defs, prepared_binop.op, *schema);
|
||||
} else {
|
||||
// Anything else
|
||||
|
||||
@@ -107,7 +107,11 @@ static std::vector<expr::expression> extract_partition_range(
|
||||
current_binary_operator = nullptr;
|
||||
}
|
||||
|
||||
void operator()(const token&) {
|
||||
void operator()(const function_call& token_fun_call) {
|
||||
if (!is_partition_token_for_schema(token_fun_call, *table_schema)) {
|
||||
on_internal_error(rlogger, "extract_partition_range(function_call)");
|
||||
}
|
||||
|
||||
with_current_binary_operator(*this, [&] (const binary_operator& b) {
|
||||
if (tokens) {
|
||||
tokens = make_conjunction(std::move(*tokens), b);
|
||||
@@ -160,10 +164,6 @@ static std::vector<expr::expression> extract_partition_range(
|
||||
on_internal_error(rlogger, "extract_partition_range(column_mutation_attribute)");
|
||||
}
|
||||
|
||||
void operator()(const function_call&) {
|
||||
on_internal_error(rlogger, "extract_partition_range(function_call)");
|
||||
}
|
||||
|
||||
void operator()(const cast&) {
|
||||
on_internal_error(rlogger, "extract_partition_range(cast)");
|
||||
}
|
||||
@@ -273,8 +273,13 @@ static std::vector<expr::expression> extract_clustering_prefix_restrictions(
|
||||
});
|
||||
}
|
||||
|
||||
void operator()(const token&) {
|
||||
// A token cannot be a clustering prefix restriction
|
||||
void operator()(const function_call& fun_call) {
|
||||
if (is_partition_token_for_schema(fun_call, *table_schema)) {
|
||||
// A token cannot be a clustering prefix restriction
|
||||
return;
|
||||
}
|
||||
|
||||
on_internal_error(rlogger, "extract_clustering_prefix_restrictions(function_call)");
|
||||
}
|
||||
|
||||
void operator()(const constant&) {}
|
||||
@@ -287,10 +292,6 @@ static std::vector<expr::expression> extract_clustering_prefix_restrictions(
|
||||
on_internal_error(rlogger, "extract_clustering_prefix_restrictions(column_mutation_attribute)");
|
||||
}
|
||||
|
||||
void operator()(const function_call&) {
|
||||
on_internal_error(rlogger, "extract_clustering_prefix_restrictions(function_call)");
|
||||
}
|
||||
|
||||
void operator()(const cast&) {
|
||||
on_internal_error(rlogger, "extract_clustering_prefix_restrictions(cast)");
|
||||
}
|
||||
@@ -1246,10 +1247,6 @@ struct multi_column_range_accumulator {
|
||||
on_internal_error(rlogger, "Subscript encountered outside binary operator");
|
||||
}
|
||||
|
||||
void operator()(const token&) {
|
||||
on_internal_error(rlogger, "Token encountered outside binary operator");
|
||||
}
|
||||
|
||||
void operator()(const unresolved_identifier&) {
|
||||
on_internal_error(rlogger, "Unresolved identifier encountered outside binary operator");
|
||||
}
|
||||
|
||||
@@ -173,18 +173,6 @@ prepare_selectable(const schema& s, const expr::expression& raw_selectable) {
|
||||
[&] (const expr::subscript& sub) -> shared_ptr<selectable> {
|
||||
on_internal_error(slogger, "no way to express 'SELECT a[b]' in the grammar yet");
|
||||
},
|
||||
[&] (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<selectable_column>(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 make_shared<selectable_column>(*ui.ident->prepare(s));
|
||||
},
|
||||
@@ -260,11 +248,6 @@ selectable_processes_selection(const expr::expression& raw_selectable) {
|
||||
// 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();
|
||||
},
|
||||
|
||||
@@ -110,6 +110,13 @@ static unresolved_identifier make_column(const char* col_name) {
|
||||
return unresolved_identifier{::make_shared<column_identifier_raw>(col_name, true)};
|
||||
}
|
||||
|
||||
static function_call make_token(std::vector<expression> args) {
|
||||
return function_call {
|
||||
.func = functions::function_name::native_function("token"),
|
||||
.args = args
|
||||
};
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(expr_printer_test) {
|
||||
expression col_eq_1234 = binary_operator(
|
||||
make_column("col"),
|
||||
@@ -119,7 +126,7 @@ BOOST_AUTO_TEST_CASE(expr_printer_test) {
|
||||
BOOST_REQUIRE_EQUAL(expr_print(col_eq_1234), "col = 1234");
|
||||
|
||||
expression token_p1_p2_lt_min_56 = binary_operator(
|
||||
token({
|
||||
make_token({
|
||||
unresolved_identifier{::make_shared<column_identifier_raw>("p1", true)},
|
||||
unresolved_identifier{::make_shared<column_identifier_raw>("p2", true)},
|
||||
}),
|
||||
@@ -1332,21 +1339,23 @@ BOOST_AUTO_TEST_CASE(prepare_token) {
|
||||
.build();
|
||||
auto [db, db_data] = make_data_dictionary_database(table_schema);
|
||||
|
||||
expression tok = token({unresolved_identifier{::make_shared<column_identifier_raw>("p1", true)},
|
||||
expression tok = make_token({unresolved_identifier{::make_shared<column_identifier_raw>("p1", true)},
|
||||
unresolved_identifier{::make_shared<column_identifier_raw>("p2", true)},
|
||||
unresolved_identifier{::make_shared<column_identifier_raw>("p3", true)}});
|
||||
|
||||
expression prepared = prepare_expression(tok, db, "test_ks", table_schema.get(), nullptr);
|
||||
|
||||
expression expected = token({column_value(table_schema->get_column_definition("p1")),
|
||||
std::vector<expression> expected_args = {column_value(table_schema->get_column_definition("p1")),
|
||||
column_value(table_schema->get_column_definition("p2")),
|
||||
column_value(table_schema->get_column_definition("p3"))});
|
||||
column_value(table_schema->get_column_definition("p3"))};
|
||||
|
||||
BOOST_REQUIRE_EQUAL(prepared, expected);
|
||||
const function_call* token_fun_call = as_if<function_call>(&prepared);
|
||||
BOOST_REQUIRE(token_fun_call != nullptr);
|
||||
BOOST_REQUIRE(is_token_function(*token_fun_call));
|
||||
BOOST_REQUIRE(std::holds_alternative<shared_ptr<db::functions::function>>(token_fun_call->func));
|
||||
BOOST_REQUIRE_EQUAL(token_fun_call->args, expected_args);
|
||||
}
|
||||
|
||||
// prepare_expression(token) doesn't validate its arguments,
|
||||
// validation is done in a different place
|
||||
BOOST_AUTO_TEST_CASE(prepare_token_no_args) {
|
||||
schema_ptr table_schema = schema_builder("test_ks", "test_cf")
|
||||
.with_column("p1", int32_type, column_kind::partition_key)
|
||||
@@ -1355,11 +1364,10 @@ BOOST_AUTO_TEST_CASE(prepare_token_no_args) {
|
||||
.build();
|
||||
auto [db, db_data] = make_data_dictionary_database(table_schema);
|
||||
|
||||
expression tok = token(std::vector<expression>());
|
||||
expression tok = make_token(std::vector<expression>());
|
||||
|
||||
expression prepared = prepare_expression(tok, db, "test_ks", table_schema.get(), nullptr);
|
||||
|
||||
BOOST_REQUIRE_EQUAL(tok, prepared);
|
||||
BOOST_REQUIRE_THROW(prepare_expression(tok, db, "test_ks", table_schema.get(), nullptr),
|
||||
exceptions::invalid_request_exception);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(prepare_cast_int_int) {
|
||||
|
||||
@@ -449,7 +449,10 @@ BOOST_AUTO_TEST_CASE(expression_extract_column_restrictions) {
|
||||
expression ck1_ck2_restriction = make_multi_column_restriction({&col_ck1, &col_ck2}, oper_t::LT);
|
||||
expression conjunction_expr = conjunction{std::vector{ck1_ck2_restriction, r1_restriction2}};
|
||||
|
||||
token token_expr(std::vector<const column_definition*>{&col_pk1, &col_pk2});
|
||||
function_call token_expr = function_call {
|
||||
.func = functions::function_name::native_function("token"),
|
||||
.args = {column_value(&col_pk1), column_value(&col_pk2)}
|
||||
};
|
||||
expression token_lt_restriction = binary_operator(token_expr, oper_t::LT, zero_value);
|
||||
expression token_gt_restriction = binary_operator(token_expr, oper_t::GT, zero_value);
|
||||
|
||||
|
||||
@@ -399,12 +399,14 @@ def test_filter_and_fetch_size(cql, test_keyspace, use_index, driver_bug_1):
|
||||
# Reproduces #13468
|
||||
def test_filter_token(cql, test_keyspace):
|
||||
with new_test_table(cql, test_keyspace, 'pk int, ck int, x int, PRIMARY KEY (pk, ck)') as table:
|
||||
with pytest.raises(InvalidRequest, match='duplicate partition key'):
|
||||
with pytest.raises(InvalidRequest, match='Invalid number of arguments'):
|
||||
cql.execute(f'SELECT pk FROM {table} WHERE token(pk, pk) = 0')
|
||||
with new_test_table(cql, test_keyspace, 'pk int, ck int, x int, PRIMARY KEY ((pk, ck))') as table:
|
||||
with pytest.raises(InvalidRequest, match='all partition key components'):
|
||||
with pytest.raises(InvalidRequest, match='duplicate partition key'):
|
||||
cql.execute(f'SELECT pk FROM {table} WHERE token(pk, pk) = 0')
|
||||
with pytest.raises(InvalidRequest, match='Invalid number of arguments'):
|
||||
cql.execute(f'SELECT pk FROM {table} WHERE token(x) = 0')
|
||||
with pytest.raises(InvalidRequest, match='all partition key components'):
|
||||
with pytest.raises(InvalidRequest, match='Invalid number of arguments'):
|
||||
cql.execute(f'SELECT pk FROM {table} WHERE token(pk) = 0')
|
||||
with pytest.raises(InvalidRequest, match='partition key order'):
|
||||
cql.execute(f'SELECT pk FROM {table} WHERE token(ck, pk) = 0')
|
||||
|
||||
Reference in New Issue
Block a user