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:
Jan Ciolek
2023-03-30 22:13:47 +02:00
parent 6e0ae59c5a
commit be8ef63bf5
10 changed files with 69 additions and 164 deletions

View File

@@ -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

View File

@@ -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");
},

View File

@@ -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

View File

@@ -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{

View File

@@ -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

View File

@@ -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");
}

View File

@@ -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();
},

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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')