Files
scylladb/test/boost/expr_test.cc
Avi Kivity d69bf4f010 cql3: introduce dialect infrastructure
A dialect is a different way to interpret the same CQL statement.

Examples:
 - how duplicate bind variable names are handled (later in this series)
 - whether `column = NULL` in LWT can return true (as is now) or
   whether it always returns NULL (as in SQL)

Currently, dialect is an empty structure and will be filled in later.
It is passed to query_processor methods that also accept a CQL string,
and from there to the parser. It is part of the prepared statement cache
key, so that if the dialect is changed online, previous parses of the
statement are ignored and the statement is prepared again.

The patch is careful to pick up the dialect at the entry point (e.g.
CQL protocol server) so that the dialect doesn't change while a statement
is parsed, prepared, and cached.
2024-08-29 21:19:23 +03:00

4676 lines
229 KiB
C++

// Copyright (C) 2023-present ScyllaDB
// SPDX-License-Identifier: AGPL-3.0-or-later
#include "cql3/column_identifier.hh"
#include "cql3/util.hh"
#include <seastar/core/shared_ptr.hh>
#include "types/types.hh"
#include "types/list.hh"
#include "types/map.hh"
#include <boost/test/tools/old/interface.hpp>
#define BOOST_TEST_MODULE core
#include <boost/test/unit_test.hpp>
#include <utility>
#include <fmt/ranges.h>
#include "cql3/expr/expression.hh"
#include "utils/overloaded_functor.hh"
#include "utils/to_string.hh"
#include <cassert>
#include "cql3/query_options.hh"
#include "types/set.hh"
#include "types/user.hh"
#include "test/lib/expr_test_utils.hh"
#include "test/lib/test_utils.hh"
#include "cql3/expr/evaluate.hh"
#include "cql3/expr/expr-utils.hh"
using namespace cql3;
using namespace cql3::expr;
using namespace cql3::expr::test_utils;
bind_variable new_bind_variable(int bind_index, data_type type = int32_type) {
return bind_variable {
.bind_index = bind_index,
.receiver = make_lw_shared<column_specification>("ks", "tab", make_shared<column_identifier>("?", true), std::move(type)),
};
}
BOOST_AUTO_TEST_CASE(expr_visit_get_int) {
expression e = new_bind_variable(1245);
int read_value = visit(overloaded_functor {
[](const bind_variable& bv) -> int { return bv.bind_index; },
[](const auto&) -> int { throw std::runtime_error("Unreachable"); }
}, e);
BOOST_REQUIRE_EQUAL(read_value, 1245);
}
BOOST_AUTO_TEST_CASE(expr_visit_void_return) {
expression e = new_bind_variable(1245);
visit(overloaded_functor {
[](const bind_variable& bv) { BOOST_REQUIRE_EQUAL(bv.bind_index, 1245); },
[](const auto&) { throw std::runtime_error("Unreachable"); }
}, e);
}
BOOST_AUTO_TEST_CASE(expr_visit_const_ref) {
const expression e = new_bind_variable(123);
const bind_variable& ref = visit(overloaded_functor {
[](const bind_variable& bv) -> const bind_variable& { return bv; },
[](const auto&) -> const bind_variable& { throw std::runtime_error("Unreachable"); }
}, e);
BOOST_REQUIRE_EQUAL(ref.bind_index, 123);
}
BOOST_AUTO_TEST_CASE(expr_visit_ref) {
expression e = new_bind_variable(456);
bind_variable& ref = visit(overloaded_functor {
[](bind_variable& bv) -> bind_variable& { return bv; },
[](auto&) -> bind_variable& { throw std::runtime_error("Unreachable"); }
}, e);
BOOST_REQUIRE_EQUAL(ref.bind_index, 456);
ref.bind_index = 135;
bind_variable& ref2 = visit(overloaded_functor {
[](bind_variable& bv) -> bind_variable& { return bv; },
[](auto&) -> bind_variable& { throw std::runtime_error("Unreachable"); }
}, e);
BOOST_REQUIRE_EQUAL(ref2.bind_index, 135);
}
struct rvalue_visitor {
rvalue_visitor(){};
rvalue_visitor(const rvalue_visitor&) = delete;
bind_variable& operator()(bind_variable& bv) && { return bv; }
bind_variable& operator()(auto&) && { throw std::runtime_error("Unreachable"); }
} v;
BOOST_AUTO_TEST_CASE(expr_visit_visitor_rvalue) {
expression e = new_bind_variable(456);
rvalue_visitor visitor;
bind_variable& ref2 = visit(std::move(visitor), e);
BOOST_REQUIRE_EQUAL(ref2.bind_index, 456);
}
static sstring expr_print(const expression& e) {
return format("{:user}", e);
}
static sstring value_print(const cql3::raw_value& v, const expression& e) {
return expr_print(constant(v, type_of(e)));
}
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"),
oper_t::EQ,
make_int_const(1234)
);
BOOST_REQUIRE_EQUAL(expr_print(col_eq_1234), "col = 1234");
expression token_p1_p2_lt_min_56 = binary_operator(
make_token({
unresolved_identifier{::make_shared<column_identifier_raw>("p1", true)},
unresolved_identifier{::make_shared<column_identifier_raw>("p2", true)},
}),
oper_t::LT,
make_int_const(-56)
);
BOOST_REQUIRE_EQUAL(expr_print(token_p1_p2_lt_min_56), "token(p1, p2) < -56");
}
BOOST_AUTO_TEST_CASE(expr_printer_string_test) {
constant utf8_val(raw_value::make_value(utf8_type->decompose("abcdef")), utf8_type);
BOOST_REQUIRE_EQUAL(expr_print(utf8_val), "'abcdef'");
constant ascii_val(raw_value::make_value(ascii_type->decompose("abcdef")), utf8_type);
BOOST_REQUIRE_EQUAL(expr_print(ascii_val), "'abcdef'");
}
BOOST_AUTO_TEST_CASE(expr_printer_inet_test) {
constant inet_const(
raw_value::make_value(inet_addr_type->from_string("1.2.3.4")),
inet_addr_type
);
BOOST_REQUIRE_EQUAL(expr_print(inet_const), "'1.2.3.4'");
}
BOOST_AUTO_TEST_CASE(expr_printer_timestamp_test) {
constant timestamp_const (
raw_value::make_value(timestamp_type->from_string("2011-03-02T03:05:00+0000")),
timestamp_type
);
BOOST_REQUIRE_EQUAL(expr_print(timestamp_const), "'2011-03-02T03:05:00.000Z'");
}
BOOST_AUTO_TEST_CASE(expr_printer_time_test) {
constant time_const (
raw_value::make_value(time_type->from_string("08:12:54")),
time_type
);
BOOST_REQUIRE_EQUAL(expr_print(time_const), "'08:12:54.000000000'");
}
BOOST_AUTO_TEST_CASE(expr_printer_date_test) {
constant date_const {
raw_value::make_value(date_type->from_string("2011-02-03+0000")),
date_type
};
BOOST_REQUIRE_EQUAL(expr_print(date_const), "'2011-02-03T00:00:00.000Z'");
}
BOOST_AUTO_TEST_CASE(expr_printer_duration_test) {
constant duration_const {
raw_value::make_value(duration_type->from_string("89h4m48s")),
duration_type
};
BOOST_REQUIRE_EQUAL(expr_print(duration_const), "89h4m48s");
}
BOOST_AUTO_TEST_CASE(expr_printer_list_test) {
collection_constructor int_list {
.style = collection_constructor::style_type::list,
.elements = {make_int_const(13), make_int_const(45), make_int_const(90)},
.type = list_type_impl::get_instance(int32_type, true)
};
BOOST_REQUIRE_EQUAL(expr_print(int_list), "[13, 45, 90]");
collection_constructor frozen_int_list {
.style = collection_constructor::style_type::list,
.elements = {make_int_const(13), make_int_const(45), make_int_const(90)},
.type = list_type_impl::get_instance(int32_type, false)
};
BOOST_REQUIRE_EQUAL(expr_print(frozen_int_list), "[13, 45, 90]");
cql3::raw_value int_list_constant = evaluate(int_list, query_options::DEFAULT);
BOOST_REQUIRE_EQUAL(value_print(int_list_constant, int_list), "[13, 45, 90]");
cql3::raw_value frozen_int_list_constant = evaluate(frozen_int_list, query_options::DEFAULT);
BOOST_REQUIRE_EQUAL(value_print(frozen_int_list_constant, frozen_int_list), "[13, 45, 90]");
}
BOOST_AUTO_TEST_CASE(expr_printer_set_test) {
collection_constructor int_set {
.style = collection_constructor::style_type::set,
.elements = {make_int_const(13), make_int_const(45), make_int_const(90)},
.type = set_type_impl::get_instance(int32_type, true)
};
BOOST_REQUIRE_EQUAL(expr_print(int_set), "{13, 45, 90}");
collection_constructor frozen_int_set {
.style = collection_constructor::style_type::set,
.elements = {make_int_const(13), make_int_const(45), make_int_const(90)},
.type = set_type_impl::get_instance(int32_type, true)
};
BOOST_REQUIRE_EQUAL(expr_print(frozen_int_set), "{13, 45, 90}");
cql3::raw_value int_set_constant = evaluate(int_set, query_options::DEFAULT);
BOOST_REQUIRE_EQUAL(value_print(int_set_constant, int_set), "{13, 45, 90}");
cql3::raw_value frozen_int_set_constant = evaluate(frozen_int_set, query_options::DEFAULT);
BOOST_REQUIRE_EQUAL(value_print(frozen_int_set_constant, frozen_int_set), "{13, 45, 90}");
}
BOOST_AUTO_TEST_CASE(expr_printer_map_test) {
collection_constructor int_int_map {
.style = collection_constructor::style_type::map,
.elements = {
tuple_constructor {
.elements = {make_int_const(12), make_int_const(34)}
},
tuple_constructor {
.elements = {make_int_const(56), make_int_const(78)}
}
},
.type = map_type_impl::get_instance(int32_type, int32_type, true)
};
BOOST_REQUIRE_EQUAL(expr_print(int_int_map), "{12:34, 56:78}");
collection_constructor frozen_int_int_map {
.style = collection_constructor::style_type::map,
.elements = int_int_map.elements,
.type = map_type_impl::get_instance(int32_type, int32_type, false)
};
BOOST_REQUIRE_EQUAL(expr_print(frozen_int_int_map), "{12:34, 56:78}");
cql3::raw_value int_int_map_const = evaluate(int_int_map, query_options::DEFAULT);
BOOST_REQUIRE_EQUAL(value_print(int_int_map_const, int_int_map), "{12:34, 56:78}");
cql3::raw_value frozen_int_int_map_const = evaluate(frozen_int_int_map, query_options::DEFAULT);
BOOST_REQUIRE_EQUAL(value_print(frozen_int_int_map_const, frozen_int_int_map), "{12:34, 56:78}");
}
BOOST_AUTO_TEST_CASE(expr_printer_tuple_test) {
tuple_constructor int_int_tuple {
.elements = {make_int_const(456), make_int_const(789)},
.type = tuple_type_impl::get_instance({int32_type, int32_type})
};
BOOST_REQUIRE_EQUAL(expr_print(int_int_tuple), "(456, 789)");
cql3::raw_value int_int_tuple_const = evaluate(int_int_tuple, query_options::DEFAULT);
BOOST_REQUIRE_EQUAL(value_print(int_int_tuple_const, int_int_tuple), "(456, 789)");
}
BOOST_AUTO_TEST_CASE(expr_printer_usertype_test) {
column_identifier field_a("a", true);
column_identifier field_b("b", true);
usertype_constructor::elements_map_type user_type_elements;
user_type_elements.emplace(field_a, make_int_const(333));
user_type_elements.emplace(field_b, make_int_const(666));
usertype_constructor user_typ {
.elements = user_type_elements,
.type = user_type_impl::get_instance("ks", "expr_test_type", {field_a.name(), field_b.name()}, {int32_type, int32_type}, true)
};
BOOST_REQUIRE_EQUAL(expr_print(user_typ), "{b:666, a:333}");
cql3::raw_value user_typ_const = evaluate(user_typ, query_options::DEFAULT);
BOOST_REQUIRE_EQUAL(value_print(user_typ_const, user_typ), "{a:333, b:666}");
}
// When a list is printed as RHS of an IN binary_operator it should be printed as a tuple.
BOOST_AUTO_TEST_CASE(expr_printer_in_test) {
collection_constructor int_list {
.style = collection_constructor::style_type::list,
.elements = {make_int_const(13), make_int_const(45), make_int_const(90)},
.type = list_type_impl::get_instance(int32_type, true)
};
binary_operator a_in_int_list {
make_column("a"),
oper_t::IN,
int_list
};
BOOST_REQUIRE_EQUAL(expr_print(a_in_int_list), "a IN (13, 45, 90)");
cql3::raw_value int_list_const = evaluate(int_list, query_options::DEFAULT);
binary_operator a_in_int_list_const {
make_column("a"),
oper_t::IN,
constant(int_list_const, type_of(int_list))
};
BOOST_REQUIRE_EQUAL(expr_print(a_in_int_list_const), "a IN (13, 45, 90)");
}
// To easily test how many expressions work with expression::printer
// We can use a function that parses a string to expression and then
// print it using another function that uses printer
BOOST_AUTO_TEST_CASE(expr_printer_parse_and_print_test) {
auto tests = {
"col = 1234",
"col != 1234",
"col < 1234",
"col <= 1234",
"col >= 1234",
"col > 1234",
"col CONTAINS 1234",
"col CONTAINS KEY 1234",
"col IS NOT null",
"col LIKE 'abc'",
"token(p1, p2) > -3434",
"col2 = (1, 2)",
"col2 = {1, 2}",
"col2 IN (1, 2, 3)",
"col2 IN ((1, 2), (3, 4))",
"(col1, col2) < (1, 2)",
"(c1, c2) IN ((1, 2), (3, 4))",
"col > ?",
"col IN (1, 2, 3, ?, 4, null)"
};
for(const char* test : tests) {
expression parsed_where = cql3::util::where_clause_to_relations(test, cql3::dialect{});
sstring printed_where = cql3::util::relations_to_where_clause(parsed_where);
BOOST_REQUIRE_EQUAL(sstring(test), printed_where);
}
}
BOOST_AUTO_TEST_CASE(boolean_factors_test) {
BOOST_REQUIRE_EQUAL(boolean_factors(make_bool_const(true)), std::vector<expression>({make_bool_const(true)}));
BOOST_REQUIRE_EQUAL(boolean_factors(constant::make_null(boolean_type)), std::vector<expression>({constant::make_null(boolean_type)}));
bind_variable bv1{0};
bind_variable bv2{1};
bind_variable bv3{2};
bind_variable bv4{3};
BOOST_REQUIRE_EQUAL(
boolean_factors(
conjunction{std::vector<expression>({bv1, bv2, bv3, bv4})}
),
std::vector<expression>(
{bv1, bv2, bv3, bv4}
)
);
BOOST_REQUIRE_EQUAL(
boolean_factors(
conjunction{
std::vector<expression>({
make_conjunction(bv1, bv2),
make_conjunction(bv3, bv4)
})
}
),
std::vector<expression>(
{bv1, bv2, bv3, bv4}
)
);
}
BOOST_AUTO_TEST_CASE(evaluate_constant_null) {
expression constant_null = constant::make_null();
BOOST_REQUIRE_EQUAL(evaluate(constant_null, evaluation_inputs{}), raw_value::make_null());
expression constant_null_with_type = constant::make_null(int32_type);
BOOST_REQUIRE_EQUAL(evaluate(constant_null_with_type, evaluation_inputs{}), raw_value::make_null());
}
BOOST_AUTO_TEST_CASE(evaluate_constant_empty) {
expression constant_empty_bool = constant(raw_value::make_value(bytes()), boolean_type);
BOOST_REQUIRE(evaluate(constant_empty_bool, evaluation_inputs{}).is_empty_value());
expression constant_empty_int = constant(raw_value::make_value(bytes()), int32_type);
BOOST_REQUIRE(evaluate(constant_empty_int, evaluation_inputs{}).is_empty_value());
expression constant_empty_text = constant(raw_value::make_value(bytes()), utf8_type);
BOOST_REQUIRE_EQUAL(evaluate(constant_empty_text, evaluation_inputs{}), make_text_raw(""));
}
BOOST_AUTO_TEST_CASE(evaluate_constant_int) {
expression const_int = make_int_const(723);
BOOST_REQUIRE_EQUAL(evaluate(const_int, evaluation_inputs{}), make_int_raw(723));
}
// Creates a schema_ptr that can be used for testing
// The schema corresponds to a table created by:
// CREATE TABLE test_ks.test_cf (pk int, ck int, r int, s int static, primary key (pk, ck));
static schema_ptr make_simple_test_schema() {
return schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("ck", int32_type, column_kind::clustering_key)
.with_column("r", int32_type, column_kind::regular_column)
.with_column("s", int32_type, column_kind::static_column)
.build();
}
// Creates a schema_ptr with three partition key columns can be used for testing
// The schema corresponds to a table created by:
// CREATE TABLE test_ks.test_cf (pk1 int, pk2 int, pk3 int, ck int, r int, s int static, primary key (pk, ck));
static schema_ptr make_three_pk_schema() {
return schema_builder("test_ks", "test_cf")
.with_column("pk1", int32_type, column_kind::partition_key)
.with_column("pk2", int32_type, column_kind::partition_key)
.with_column("pk3", int32_type, column_kind::partition_key)
.with_column("ck", int32_type, column_kind::clustering_key)
.with_column("r", int32_type, column_kind::regular_column)
.with_column("s", int32_type, column_kind::static_column)
.build();
}
BOOST_AUTO_TEST_CASE(evaluate_partition_key_column) {
schema_ptr test_schema = make_simple_test_schema();
auto [inputs, inputs_data] = make_evaluation_inputs(test_schema, {
{"pk", make_int_raw(1)},
{"ck", make_int_raw(2)},
{"r", make_int_raw(3)},
{"s", make_int_raw(4)},
});
expression pk_val = column_value(test_schema->get_column_definition("pk"));
raw_value val = evaluate(pk_val, inputs);
BOOST_REQUIRE_EQUAL(val, make_int_raw(1));
}
BOOST_AUTO_TEST_CASE(evaluate_clustering_key_column) {
schema_ptr test_schema = make_simple_test_schema();
auto [inputs, inputs_data] = make_evaluation_inputs(test_schema, {
{"pk", make_int_raw(1)},
{"ck", make_int_raw(2)},
{"r", make_int_raw(3)},
{"s", make_int_raw(4)},
});
expression ck_val = column_value(test_schema->get_column_definition("ck"));
raw_value val = evaluate(ck_val, inputs);
BOOST_REQUIRE_EQUAL(val, make_int_raw(2));
}
BOOST_AUTO_TEST_CASE(evaluate_regular_column) {
schema_ptr test_schema = make_simple_test_schema();
auto [inputs, inputs_data] = make_evaluation_inputs(test_schema, {
{"pk", make_int_raw(1)},
{"ck", make_int_raw(2)},
{"r", make_int_raw(3)},
{"s", make_int_raw(4)},
});
expression r_val = column_value(test_schema->get_column_definition("r"));
raw_value val = evaluate(r_val, inputs);
BOOST_REQUIRE_EQUAL(val, make_int_raw(3));
}
BOOST_AUTO_TEST_CASE(evaluate_static_column) {
schema_ptr test_schema = make_simple_test_schema();
auto [inputs, inputs_data] = make_evaluation_inputs(test_schema, {
{"pk", make_int_raw(1)},
{"ck", make_int_raw(2)},
{"r", make_int_raw(3)},
{"s", make_int_raw(4)},
});
expression s_val = column_value(test_schema->get_column_definition("s"));
raw_value val = evaluate(s_val, inputs);
BOOST_REQUIRE_EQUAL(val, make_int_raw(4));
}
BOOST_AUTO_TEST_CASE(evaluate_column_value_does_not_perfrom_validation) {
schema_ptr test_schema =
schema_builder("test_ks", "test_cf").with_column("pk", int32_type, column_kind::partition_key).build();
raw_value invalid_int_value = make_bool_raw(true);
auto [inputs, inputs_data] = make_evaluation_inputs(test_schema, {{"pk", invalid_int_value}});
expression pk_column = column_value(test_schema->get_column_definition("pk"));
raw_value val = evaluate(pk_column, inputs);
BOOST_REQUIRE_EQUAL(val, invalid_int_value);
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable) {
schema_ptr test_schema = make_simple_test_schema();
auto [inputs, inputs_data] = make_evaluation_inputs(test_schema,
{
{"pk", make_int_raw(1)},
{"ck", make_int_raw(2)},
{"r", make_int_raw(3)},
{"s", make_int_raw(4)},
},
{make_int_raw(123)});
expression bind_var = bind_variable{.bind_index = 0, .receiver = make_receiver(int32_type, "bind_var_0")};
raw_value val = evaluate(bind_var, inputs);
BOOST_REQUIRE_EQUAL(val, make_int_raw(123));
}
BOOST_AUTO_TEST_CASE(evaluate_two_bind_variables) {
schema_ptr test_schema = make_simple_test_schema();
auto [inputs, inputs_data] = make_evaluation_inputs(test_schema,
{
{"pk", make_int_raw(1)},
{"ck", make_int_raw(2)},
{"r", make_int_raw(3)},
{"s", make_int_raw(4)},
},
{make_int_raw(123), make_int_raw(456)});
expression bind_variable0 = bind_variable{.bind_index = 0, .receiver = make_receiver(int32_type, "bind_var_0")};
expression bind_variable1 = bind_variable{.bind_index = 1, .receiver = make_receiver(int32_type, "bind_var_1")};
raw_value val0 = evaluate(bind_variable0, inputs);
BOOST_REQUIRE_EQUAL(val0, make_int_raw(123));
raw_value val1 = evaluate(bind_variable1, inputs);
BOOST_REQUIRE_EQUAL(val1, make_int_raw(456));
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_performs_validation) {
schema_ptr test_schema =
schema_builder("test_ks", "test_cf").with_column("pk", int32_type, column_kind::partition_key).build();
raw_value invalid_int_value = make_bool_raw(true);
expression bind_var = bind_variable{.bind_index = 0, .receiver = make_receiver(int32_type, "bind_var")};
auto [inputs, inputs_data] = make_evaluation_inputs(test_schema, {{"pk", make_int_raw(123)}}, {invalid_int_value});
BOOST_REQUIRE_THROW(evaluate(bind_var, inputs), exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_vs_unset) {
auto qo = query_options(cql3::raw_value_vector_with_unset({raw_value::make_null()}, {true}));
BOOST_REQUIRE_THROW(evaluate(new_bind_variable(0), evaluation_inputs{.options = &qo}), exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(evaluate_list_collection_constructor_empty) {
// TODO: Empty multi-cell collections are trated as NULL in the database,
// should the conversion happen in evaluate?
expression empty_list = make_list_constructor({}, int32_type);
BOOST_REQUIRE_EQUAL(evaluate(empty_list, evaluation_inputs{}), make_int_list_raw({}));
}
BOOST_AUTO_TEST_CASE(evaluate_list_collection_constructor) {
expression int_list = make_list_constructor({make_int_const(1), make_int_const(2), make_int_const(3)}, int32_type);
BOOST_REQUIRE_EQUAL(evaluate(int_list, evaluation_inputs{}), make_int_list_raw({1, 2, 3}));
}
BOOST_AUTO_TEST_CASE(evaluate_list_collection_constructor_does_not_sort) {
expression int_list =
make_list_constructor({make_int_const(3), make_int_const(1), make_int_const(3), make_int_const(1)}, int32_type);
BOOST_REQUIRE_EQUAL(evaluate(int_list, evaluation_inputs{}), make_int_list_raw({3, 1, 3, 1}));
}
BOOST_AUTO_TEST_CASE(evaluate_list_collection_constructor_with_null) {
expression list_with_null =
make_list_constructor({make_int_const(1), constant::make_null(int32_type), make_int_const(3)}, int32_type);
BOOST_REQUIRE_EQUAL(evaluate(list_with_null, evaluation_inputs{}), make_int_list_raw({1, std::nullopt, 3}));
}
BOOST_AUTO_TEST_CASE(evaluate_list_collection_constructor_with_empty_value) {
expression list_with_empty =
make_list_constructor({make_int_const(1), make_empty_const(int32_type), make_int_const(3)}, int32_type);
BOOST_REQUIRE_EQUAL(evaluate(list_with_empty, evaluation_inputs{}),
make_list_raw({make_int_raw(1), make_empty_raw(), make_int_raw(3)}));
}
BOOST_AUTO_TEST_CASE(evaluate_set_collection_constructor_empty) {
// TODO: Empty multi-cell collections are trated as NULL in the database,
// should the conversion happen in evaluate?
expression empty_set = make_set_constructor({}, int32_type);
BOOST_REQUIRE_EQUAL(evaluate(empty_set, evaluation_inputs{}), make_int_set_raw({}));
}
BOOST_AUTO_TEST_CASE(evaluate_set_collection_constructor_sorted) {
expression sorted_set = make_set_constructor({make_int_const(1), make_int_const(2), make_int_const(3)}, int32_type);
BOOST_REQUIRE_EQUAL(evaluate(sorted_set, evaluation_inputs{}), make_int_set_raw({1, 2, 3}));
}
BOOST_AUTO_TEST_CASE(evaluate_set_collection_constructor_unsorted) {
expression unsorted_set =
make_set_constructor({make_int_const(1), make_int_const(3), make_int_const(2)}, int32_type);
BOOST_REQUIRE_EQUAL(evaluate(unsorted_set, evaluation_inputs{}), make_int_set_raw({1, 2, 3}));
}
BOOST_AUTO_TEST_CASE(evaluate_set_collection_constructor_with_null) {
expression set_with_null =
make_set_constructor({make_int_const(1), constant::make_null(int32_type), make_int_const(3)}, int32_type);
BOOST_REQUIRE_THROW(evaluate(set_with_null, evaluation_inputs{}), exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(evaluate_set_collection_constructor_with_empty) {
expression set_with_only_one_empty = make_set_constructor({make_empty_const(int32_type)}, int32_type);
BOOST_REQUIRE_EQUAL(evaluate(set_with_only_one_empty, evaluation_inputs{}), make_set_raw({make_empty_raw()}));
expression set_two_with_empty =
make_set_constructor({make_int_const(-1), make_empty_const(int32_type), make_int_const(0),
make_empty_const(int32_type), make_int_const(1)},
int32_type);
BOOST_REQUIRE_EQUAL(evaluate(set_two_with_empty, evaluation_inputs{}),
make_set_raw({make_empty_raw(), make_int_raw(-1), make_int_raw(0), make_int_raw(1)}));
}
BOOST_AUTO_TEST_CASE(evaluate_map_collection_constructor_with_empty) {
// TODO: Empty multi-cell collections are trated as NULL in the database,
// should the conversion happen in evaluate?
expression empty_map = make_map_constructor(std::vector<expression>(), int32_type, int32_type);
BOOST_REQUIRE_EQUAL(evaluate(empty_map, evaluation_inputs{}), make_int_int_map_raw({}));
}
BOOST_AUTO_TEST_CASE(evaluate_map_collection_constructor_sorted) {
expression map = make_map_constructor(
{
{make_int_const(1), make_int_const(2)},
{make_int_const(3), make_int_const(4)},
{make_int_const(5), make_int_const(6)},
},
int32_type, int32_type);
BOOST_REQUIRE_EQUAL(evaluate(map, evaluation_inputs{}), make_int_int_map_raw({{1, 2}, {3, 4}, {5, 6}}));
}
BOOST_AUTO_TEST_CASE(evaluate_map_collection_constructor_unsorted) {
expression map = make_map_constructor(
{
{make_int_const(3), make_int_const(4)},
{make_int_const(5), make_int_const(6)},
{make_int_const(1), make_int_const(2)},
},
int32_type, int32_type);
BOOST_REQUIRE_EQUAL(evaluate(map, evaluation_inputs{}), make_int_int_map_raw({{1, 2}, {3, 4}, {5, 6}}));
}
BOOST_AUTO_TEST_CASE(evaluate_map_collection_constructor_with_null_key) {
expression map_with_null_key = make_map_constructor(
{
{make_int_const(1), make_int_const(2)},
{constant::make_null(int32_type), make_int_const(4)},
{make_int_const(5), make_int_const(6)},
},
int32_type, int32_type);
BOOST_REQUIRE_THROW(evaluate(map_with_null_key, evaluation_inputs{}), exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(evaluate_map_collection_constructor_with_null_value) {
expression map_with_null_value = make_map_constructor(
{
{make_int_const(1), make_int_const(2)},
{make_int_const(3), constant::make_null(int32_type)},
{make_int_const(5), make_int_const(6)},
},
int32_type, int32_type);
BOOST_REQUIRE_THROW(evaluate(map_with_null_value, evaluation_inputs{}), exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(evaluate_map_collection_constructor_with_empty_key) {
expression map_with_empty_key = make_map_constructor(
{
{make_int_const(1), make_int_const(2)},
{make_empty_const(int32_type), make_int_const(4)},
{make_int_const(5), make_int_const(6)},
},
int32_type, int32_type);
raw_value expected = make_map_raw(
{{make_empty_raw(), make_int_raw(4)}, {make_int_raw(1), make_int_raw(2)}, {make_int_raw(5), make_int_raw(6)}});
BOOST_REQUIRE_EQUAL(evaluate(map_with_empty_key, evaluation_inputs{}), expected);
}
BOOST_AUTO_TEST_CASE(evaluate_map_collection_constructor_with_empty_value) {
expression map_with_empty_key = make_map_constructor(
{
{make_int_const(1), make_int_const(2)},
{make_int_const(3), make_empty_const(int32_type)},
{make_int_const(5), make_int_const(6)},
},
int32_type, int32_type);
raw_value expected = make_map_raw(
{{make_int_raw(1), make_int_raw(2)}, {make_int_raw(3), make_empty_raw()}, {make_int_raw(5), make_int_raw(6)}});
BOOST_REQUIRE_EQUAL(evaluate(map_with_empty_key, evaluation_inputs{}), expected);
}
BOOST_AUTO_TEST_CASE(evaluate_tuple_constructor_empty) {
expression empty_tuple = make_tuple_constructor({}, {});
BOOST_REQUIRE_EQUAL(evaluate(empty_tuple, evaluation_inputs{}), make_tuple_raw({}));
}
BOOST_AUTO_TEST_CASE(evaluate_tuple_constructor_empty_prefix) {
// It's legal for tuples to not have all fields present.
expression empty_int_text_tuple = make_tuple_constructor({}, {int32_type, utf8_type});
BOOST_REQUIRE_EQUAL(evaluate(empty_int_text_tuple, evaluation_inputs{}), make_tuple_raw({}));
}
BOOST_AUTO_TEST_CASE(evaluate_tuple_constructor_int_text) {
expression int_text_tuple =
make_tuple_constructor({make_int_const(123), make_text_const("tupled")}, {int32_type, utf8_type});
BOOST_REQUIRE_EQUAL(evaluate(int_text_tuple, evaluation_inputs{}),
make_tuple_raw({make_int_raw(123), make_text_raw("tupled")}));
}
BOOST_AUTO_TEST_CASE(evaluate_tuple_constructor_with_null) {
expression tuple_with_null =
make_tuple_constructor({make_int_const(12), constant::make_null(int32_type), make_int_const(34)},
{int32_type, int32_type, int32_type});
BOOST_REQUIRE_EQUAL(evaluate(tuple_with_null, evaluation_inputs{}),
make_tuple_raw({make_int_raw(12), raw_value::make_null(), make_int_raw(34)}));
}
BOOST_AUTO_TEST_CASE(evaluate_tuple_constructor_with_empty) {
expression tuple_with_empty = make_tuple_constructor(
{make_int_const(12), make_empty_const(int32_type), make_int_const(34)}, {int32_type, utf8_type, int32_type});
BOOST_REQUIRE_EQUAL(evaluate(tuple_with_empty, evaluation_inputs{}),
make_tuple_raw({make_int_raw(12), make_empty_raw(), make_int_raw(34)}));
}
BOOST_AUTO_TEST_CASE(evaluate_tuple_constructor_with_prefix_fields) {
// Tests evaluating a value of type tuple<int, text, int, double>, but only two fields are present
expression tuple = make_tuple_constructor({make_int_const(1), make_text_const("12")},
{int32_type, utf8_type, int32_type, double_type});
BOOST_REQUIRE_EQUAL(evaluate(tuple, evaluation_inputs{}), make_tuple_raw({make_int_raw(1), make_text_raw("12")}));
}
BOOST_AUTO_TEST_CASE(evaluate_usertype_constructor_empty) {
expression empty_usertype = make_usertype_constructor({});
BOOST_REQUIRE_EQUAL(evaluate(empty_usertype, evaluation_inputs{}), make_tuple_raw({}));
}
BOOST_AUTO_TEST_CASE(evaluate_usertype_constructor) {
expression usertype = make_usertype_constructor(
{{"field1", make_int_const(123)}, {"field2", make_text_const("field2val")}, {"field3", make_bool_const(true)}});
BOOST_REQUIRE_EQUAL(evaluate(usertype, evaluation_inputs{}),
make_tuple_raw({make_int_raw(123), make_text_raw("field2val"), make_bool_raw(true)}));
}
BOOST_AUTO_TEST_CASE(evaluate_usertype_constructor_with_null) {
expression usertype_with_null = make_usertype_constructor({{"field1", make_int_const(123)},
{"field2", constant::make_null(utf8_type)},
{"field3", make_bool_const(true)}});
BOOST_REQUIRE_EQUAL(evaluate(usertype_with_null, evaluation_inputs{}),
make_tuple_raw({make_int_raw(123), raw_value::make_null(), make_bool_raw(true)}));
}
BOOST_AUTO_TEST_CASE(evaluate_usertype_constructor_with_empty) {
expression usertype_with_null = make_usertype_constructor(
{{"field1", make_int_const(123)}, {"field2", make_empty_const(utf8_type)}, {"field3", make_bool_const(true)}});
BOOST_REQUIRE_EQUAL(evaluate(usertype_with_null, evaluation_inputs{}),
make_tuple_raw({make_int_raw(123), make_empty_raw(), make_bool_raw(true)}));
}
// Evaluates value[subscript_value]
static raw_value evaluate_subscripted(constant value, constant subscript_value) {
// For now it's only possible to subscript columns, not values, so this is tested.
schema_ptr table_schema = schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("v", value.type, column_kind::regular_column)
.build();
const column_definition* value_col = table_schema->get_column_definition("v");
expression sub = subscript{.val = column_value(value_col), .sub = subscript_value};
auto [inputs, inputs_data] = make_evaluation_inputs(table_schema, {{"pk", make_int_raw(0)}, {"v", value.value}});
return evaluate(sub, inputs);
}
BOOST_AUTO_TEST_CASE(evalaute_subscripted_empty_list) {
constant list = make_list_const(std::vector<constant>{}, int32_type);
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(0)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(1)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(4)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(std::numeric_limits<int32_t>::max())),
raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(-1)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(-4)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(std::numeric_limits<int32_t>::min())),
raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, constant::make_null(int32_type)), raw_value::make_null());
// TODO: Should empty value list indexes cause an error? Why not return NULL?
BOOST_REQUIRE_THROW(evaluate_subscripted(list, make_empty_const(int32_type)), empty_value_exception);
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_list_empty) {
// Empty list values seem to not be allowed.
constant list = make_empty_const(list_type_impl::get_instance(int32_type, true));
BOOST_REQUIRE_THROW(evaluate_subscripted(list, make_int_const(0)), marshal_exception);
}
constant make_subscript_test_list() {
return make_list_const({make_int_const(357), make_int_const(468), make_empty_const(int32_type), make_int_const(123)},
int32_type);
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_list_basic) {
constant list = make_subscript_test_list();
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(0)), make_int_raw(357));
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(1)), make_int_raw(468));
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(3)), make_int_raw(123));
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_list_empty_value) {
constant list = make_subscript_test_list();
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(2)), make_empty_raw());
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_list_negative_index) {
constant list = make_subscript_test_list();
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(-1)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(-2)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(-3)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(-1000)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(std::numeric_limits<int32_t>::min())),
raw_value::make_null());
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_list_too_big_index) {
constant list = make_subscript_test_list();
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(5)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(6)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(7)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(1000)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(std::numeric_limits<int32_t>::max())),
raw_value::make_null());
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_list_null_index) {
constant list = make_subscript_test_list();
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, constant::make_null(int32_type)), raw_value::make_null());
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_list_empty_index) {
constant list = make_subscript_test_list();
// TODO: Should empty value list indexes cause an error? Why not return NULL?
BOOST_REQUIRE_THROW(evaluate_subscripted(list, make_empty_const(int32_type)), empty_value_exception);
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_list_null_list) {
constant list = constant::make_null(list_type_impl::get_instance(int32_type, true));
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_int_const(0)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, make_empty_const(int32_type)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(list, constant::make_null(int32_type)), raw_value::make_null());
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_empty_map) {
constant map = make_map_const(std::vector<std::pair<constant, constant>>(), int32_type, int32_type);
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(0)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(1)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(-1)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(-4)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(4)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(std::numeric_limits<int32_t>::min())),
raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(std::numeric_limits<int32_t>::max())),
raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, constant::make_null(int32_type)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_empty_const(int32_type)), raw_value::make_null());
}
static constant make_subscript_test_map() {
return make_map_const({{make_empty_const(int32_type), make_int_const(1)},
{make_int_const(2), make_int_const(3)},
{make_int_const(4), make_empty_const(int32_type)},
{make_int_const(6), make_int_const(7)}},
int32_type, int32_type);
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_map_basic) {
constant map = make_subscript_test_map();
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(2)), make_int_raw(3));
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(6)), make_int_raw(7));
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_map_nonexistant_key) {
constant map = make_subscript_test_map();
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(3)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(5)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(-1)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(-1000)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(1000)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(std::numeric_limits<int32_t>::min())),
raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(std::numeric_limits<int32_t>::max())),
raw_value::make_null());
}
BOOST_AUTO_TEST_CASE(evalute_subscripted_map_empty_key) {
constant map = make_subscript_test_map();
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(4)), make_empty_raw());
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_map_empty_value) {
constant map = make_subscript_test_map();
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_empty_const(int32_type)), make_int_raw(1));
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_map_null_index) {
constant map = make_subscript_test_map();
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, constant::make_null(int32_type)), raw_value::make_null());
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_map_empty) {
// Empty list values seem to not be allowed.
constant map = make_empty_const(map_type_impl::get_instance(int32_type, int32_type, true));
BOOST_REQUIRE_THROW(evaluate_subscripted(map, make_int_const(0)), marshal_exception);
}
BOOST_AUTO_TEST_CASE(evaluate_subscripted_map_null_map) {
constant map = constant::make_null(map_type_impl::get_instance(int32_type, int32_type, true));
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(0)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_empty_const(int32_type)), raw_value::make_null());
BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, constant::make_null(int32_type)), raw_value::make_null());
}
enum expected_invalid_or_valid { expected_valid, expected_invalid };
// Checks that trying to evaluate a bind variable with this value succeeds or fails.
// This is used to test bind variable validation.
static void check_bind_variable_evaluate(constant check_value, expected_invalid_or_valid expected_validity) {
schema_ptr test_schema = schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("r", check_value.type, column_kind::regular_column)
.build();
expression bind_var = bind_variable{.bind_index = 0, .receiver = make_receiver(check_value.type, "bind_var")};
auto [inputs, inputs_data] = make_evaluation_inputs(
test_schema, {{"pk", make_int_raw(0)}, {"r", cql3::raw_value::make_null()}}, {check_value.value});
switch (expected_validity) {
case expected_valid:
BOOST_REQUIRE_EQUAL(evaluate(bind_var, inputs), check_value.value);
return;
case expected_invalid:
BOOST_REQUIRE_THROW(evaluate(bind_var, inputs), exceptions::invalid_request_exception);
return;
}
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_null_in_list) {
constant list_with_null =
make_list_const({make_int_const(1), constant::make_null(int32_type), make_int_const(2)}, int32_type);
check_bind_variable_evaluate(list_with_null, expected_valid);
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_empty_in_list) {
constant lis_with_empty =
make_list_const({make_int_const(1), make_empty_const(int32_type), make_int_const(2)}, int32_type);
check_bind_variable_evaluate(lis_with_empty, expected_valid);
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_no_null_in_set) {
constant set_with_null =
make_set_const({make_int_const(1), constant::make_null(int32_type), make_int_const(2)}, int32_type);
check_bind_variable_evaluate(set_with_null, expected_invalid);
}
// TODO: This fails, but I feel like this is a bug.
// BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_empty_in_set) {
// constant set_with_empty =
// make_set_const({make_empty_const(int32_type), make_int_const(1), make_int_const(2)}, int32_type);
// check_bind_variable_evaluate(set_with_empty, expected_valid);
// }
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_no_null_key_in_map) {
constant map_with_null_key = make_map_const({{make_int_const(1), make_int_const(2)},
{constant::make_null(int32_type), make_int_const(4)},
{make_int_const(5), make_int_const(6)}},
int32_type, int32_type);
check_bind_variable_evaluate(map_with_null_key, expected_invalid);
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_empty_key_in_map) {
constant map_with_empty_key = make_map_const({{make_empty_const(int32_type), make_int_const(4)},
{make_int_const(1), make_int_const(2)},
{make_int_const(5), make_int_const(6)}},
int32_type, int32_type);
check_bind_variable_evaluate(map_with_empty_key, expected_valid);
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_no_null_value_in_map) {
constant map_with_null_value = make_map_const({{make_int_const(1), make_int_const(2)},
{make_int_const(3), constant::make_null(int32_type)},
{make_int_const(5), make_int_const(6)}},
int32_type, int32_type);
check_bind_variable_evaluate(map_with_null_value, expected_invalid);
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_empty_value_in_map) {
constant map_with_empty_value = make_map_const({{make_int_const(1), make_int_const(2)},
{make_int_const(3), make_empty_const(int32_type)},
{make_int_const(5), make_int_const(6)}},
int32_type, int32_type);
check_bind_variable_evaluate(map_with_empty_value, expected_valid);
}
// Creates a list value of the following form:
// [
// [[9, 10, 11], [8, 9, 10], [7, 8, 9]],
// [[8, 9, 10], [7, value_in_list, 9], [6, 7, 8]],
// [[7, 8, 9], [6, 7, 8], [5, 6, 7]]
// ]
// Used to check that validation recurses into the list
static constant create_nested_list_with_value(constant value_in_list) {
data_type int_list_type = list_type_impl::get_instance(int32_type, true);
constant first_list = make_list_const(
{make_int_list_const({9, 10, 11}), make_int_list_const({8, 9, 10}), make_int_list_const({7, 8, 9})},
int_list_type);
constant list_with_value = make_list_const({make_int_const(7), value_in_list, make_int_const(9)}, int32_type);
constant second_list = make_list_const(
{make_int_list_const({8, 9, 10}), list_with_value, make_int_list_const({6, 7, 8})}, int_list_type);
constant third_list = make_list_const(
{make_int_list_const({7, 8, 9}), make_int_list_const({6, 7, 8}), make_int_list_const({5, 6, 7})},
int_list_type);
return make_list_const({first_list, second_list, third_list}, first_list.type);
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_null_in_lists_recursively) {
constant list_with_null = create_nested_list_with_value(constant::make_null(int32_type));
check_bind_variable_evaluate(list_with_null, expected_valid);
}
// TODO: This fails, but I feel like this is a bug.
// BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_empty_in_lists_recursively) {
// constant list_with_empty = create_nested_list_or_set_with_value(make_empty_const(int32_type));
// check_bind_variable_evaluate(list_with_empty, expected_valid);
// }
// Creates a set value of the following form:
// {
// {{value_in_set, 2, 3}, {2, 3, 4}, {3, 4, 5}}
// {{10, 20, 30}, {20, 30, 40}, {30, 40, 50}},
// {{100, 200, 300}, {200, 300, 400}, {300, 400, 500}}
// }
// Used to check that validation recurses into the set
static constant create_nested_set_with_value(constant value_in_set) {
data_type set_of_ints_type = set_type_impl::get_instance(int32_type, true);
constant set_with_value = make_set_const({value_in_set, make_int_const(2), make_int_const(3)}, int32_type);
constant first_set = make_set_const({set_with_value, make_int_set_const({2, 3, 4}), make_int_set_const({3, 4, 5})},
set_of_ints_type);
constant second_set = make_set_const(
{make_int_set_const({10, 20, 30}), make_int_set_const({20, 30, 40}), make_int_set_const({30, 40, 50})},
set_of_ints_type);
constant third_set = make_set_const(
{make_int_set_const({100, 200, 300}), make_int_set_const({200, 300, 400}), make_int_set_const({300, 400, 500})},
set_of_ints_type);
return make_set_const({first_set, second_set, third_set}, first_set.type);
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_null_in_sets_recursively) {
constant set_with_null = create_nested_set_with_value(constant::make_null(int32_type));
check_bind_variable_evaluate(set_with_null, expected_invalid);
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_empty_in_sets_recursively) {
constant set_with_empty = create_nested_set_with_value(make_empty_const(int32_type));
check_bind_variable_evaluate(set_with_empty, expected_valid);
}
// Creates a map value of the following form:
// {
// {{key1: 2, 2: 3}: {2: 3, 3: 4}, {5: 6, 7:8}: {9:10, 11:12}}
// :
// {{key2: 14, 15: 16}: {17: 18, 19: 20}, {21: 22, 23: 24}: {25: 26, 27: 28}}},
// {{29: 30, 31: 32}: {33: 34, 35: 36}, {37: 38, 39: 40}: {41: 42, 43: 44}}
// :
// {{45: 46, 47: 48}: {49: 50, 51: 52}, {53: 54, 55: 56}: {57: 58, 59: 60}}
// }
// Used to check that validation recurses into the map
constant create_nested_map_with_key(constant key1, constant key2) {
auto i = [](int32_t i) -> constant { return make_int_const(i); };
constant key1_map = make_map_const({{key1, i(2)}, {i(2), i(3)}}, int32_type, int32_type);
constant map1 =
make_map_const({{key1_map, make_int_int_map_const({{2, 3}, {3, 4}})},
{make_int_int_map_const({{5, 6}, {7, 8}}), make_int_int_map_const({{9, 10}, {11, 12}})}},
key1_map.type, key1_map.type);
constant key2_map = make_map_const({{key2, i(14)}, {i(15), i(16)}}, int32_type, int32_type);
constant map2 =
make_map_const({{key2_map, make_int_int_map_const({{17, 18}, {19, 20}})},
{make_int_int_map_const({{21, 22}, {23, 24}}), make_int_int_map_const({{25, 26}, {27, 28}})}},
key1_map.type, key1_map.type);
constant map3 =
make_map_const({{make_int_int_map_const({{29, 30}, {31, 32}}), make_int_int_map_const({{33, 34}, {35, 36}})},
{make_int_int_map_const({{37, 38}, {39, 40}}), make_int_int_map_const({{41, 42}, {43, 44}})}},
key1_map.type, key1_map.type);
constant map4 =
make_map_const({{make_int_int_map_const({{45, 46}, {47, 48}}), make_int_int_map_const({{49, 50}, {51, 52}})},
{make_int_int_map_const({{53, 54}, {55, 56}}), make_int_int_map_const({{57, 58}, {59, 60}})}},
key1_map.type, key1_map.type);
return make_map_const({{map1, map2}, {map3, map4}}, map1.type, map1.type);
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_null_key_in_maps_recursively) {
constant map_with_null_key1 = create_nested_map_with_key(constant::make_null(int32_type), make_int_const(13));
check_bind_variable_evaluate(map_with_null_key1, expected_invalid);
constant map_with_null_key2 = create_nested_map_with_key(make_int_const(1), constant::make_null(int32_type));
check_bind_variable_evaluate(map_with_null_key2, expected_invalid);
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_empty_key_in_maps_recursively) {
constant map_with_empty_key1 = create_nested_map_with_key(make_empty_const(int32_type), make_int_const(13));
check_bind_variable_evaluate(map_with_empty_key1, expected_valid);
constant map_with_empty_key2 = create_nested_map_with_key(make_int_const(1), make_empty_const(int32_type));
check_bind_variable_evaluate(map_with_empty_key2, expected_valid);
}
// Creates a map value of the following form:
// {
// {{1: val1, 2: 3}: {2: 3, 3: 4}, {5: 6, 7:8}: {9:10, 11:12}}
// :
// {{13: val2, 15: 16}: {17: 18, 19: 20}, {21: 22, 23: 24}: {25: 26, 27: 28}}},
// {{29: 30, 31: 32}: {33: 34, 35: 36}, {37: 38, 39: 40}: {41: 42, 43: 44}}
// :
// {{45: 46, 47: 48}: {49: 50, 51: 52}, {53: 54, 55: 56}: {57: 58, 59: 60}}
// }
// Used to check that validation recurses into the map
constant create_nested_map_with_value(constant val1, constant val2) {
auto i = [](int32_t i) -> constant { return make_int_const(i); };
constant val1_map = make_map_const({{i(1), val1}, {i(2), i(3)}}, int32_type, int32_type);
constant map1 =
make_map_const({{val1_map, make_int_int_map_const({{2, 3}, {3, 4}})},
{make_int_int_map_const({{5, 6}, {7, 8}}), make_int_int_map_const({{9, 10}, {11, 12}})}},
val1_map.type, val1_map.type);
constant val2_map = make_map_const({{i(13), val2}, {i(15), i(16)}}, int32_type, int32_type);
constant map2 =
make_map_const({{val2_map, make_int_int_map_const({{17, 18}, {19, 20}})},
{make_int_int_map_const({{21, 22}, {23, 24}}), make_int_int_map_const({{25, 26}, {27, 28}})}},
val1_map.type, val1_map.type);
constant map3 =
make_map_const({{make_int_int_map_const({{29, 30}, {31, 32}}), make_int_int_map_const({{33, 34}, {35, 36}})},
{make_int_int_map_const({{37, 38}, {39, 40}}), make_int_int_map_const({{41, 42}, {43, 44}})}},
val1_map.type, val1_map.type);
constant map4 =
make_map_const({{make_int_int_map_const({{45, 46}, {47, 48}}), make_int_int_map_const({{49, 50}, {51, 52}})},
{make_int_int_map_const({{53, 54}, {55, 56}}), make_int_int_map_const({{57, 58}, {59, 60}})}},
val1_map.type, val1_map.type);
return make_map_const({{map1, map2}, {map3, map4}}, map1.type, map1.type);
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_null_value_in_maps_recursively) {
constant map_with_null_value1 = create_nested_map_with_value(constant::make_null(int32_type), make_int_const(13));
check_bind_variable_evaluate(map_with_null_value1, expected_invalid);
constant map_with_null_value2 = create_nested_map_with_value(make_int_const(1), constant::make_null(int32_type));
check_bind_variable_evaluate(map_with_null_value2, expected_invalid);
}
BOOST_AUTO_TEST_CASE(evaluate_bind_variable_validates_empty_value_in_maps_recursively) {
constant map_with_empty_value1 = create_nested_map_with_value(make_empty_const(int32_type), make_int_const(13));
check_bind_variable_evaluate(map_with_empty_value1, expected_valid);
constant map_with_empty_value2 = create_nested_map_with_value(make_int_const(1), make_empty_const(int32_type));
check_bind_variable_evaluate(map_with_empty_value2, expected_valid);
}
BOOST_AUTO_TEST_CASE(prepare_partition_column_unresolved_identifier) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression pk_unresolved_identifier =
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("pk", true)};
expression prepared = prepare_expression(pk_unresolved_identifier, db, "test_ks", table_schema.get(), nullptr);
expression expected = column_value(table_schema->get_column_definition("pk"));
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_clustering_column_unresolved_identifier) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression ck_unresolved_identifier =
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("ck", true)};
expression prepared = prepare_expression(ck_unresolved_identifier, db, "test_ks", table_schema.get(), nullptr);
expression expected = column_value(table_schema->get_column_definition("ck"));
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_regular_column_unresolved_identifier) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression r_unresolved_identifier =
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("r", true)};
expression prepared = prepare_expression(r_unresolved_identifier, db, "test_ks", table_schema.get(), nullptr);
expression expected = column_value(table_schema->get_column_definition("r"));
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_static_column_unresolved_identifier) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression s_unresolved_identifier =
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("s", true)};
expression prepared = prepare_expression(s_unresolved_identifier, db, "test_ks", table_schema.get(), nullptr);
expression expected = column_value(table_schema->get_column_definition("s"));
BOOST_REQUIRE_EQUAL(prepared, expected);
}
// prepare_expression for a column_value should do nothing
BOOST_AUTO_TEST_CASE(prepare_column_value) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression cval = column_value(table_schema->get_column_definition("pk"));
expression prepared = prepare_expression(cval, db, "test_ks", table_schema.get(), nullptr);
BOOST_REQUIRE_EQUAL(cval, prepared);
}
BOOST_AUTO_TEST_CASE(prepare_subscript_list) {
schema_ptr table_schema =
schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("r", list_type_impl::get_instance(boolean_type, true), column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression sub =
subscript{.val = unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("r", true)},
.sub = make_int_untyped("123")};
expression prepared = prepare_expression(sub, db, "test_ks", table_schema.get(), nullptr);
expression expected = subscript{.val = column_value(table_schema->get_column_definition("r")),
.sub = make_int_const(123),
.type = boolean_type};
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_subscript_map) {
schema_ptr table_schema =
schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("r", map_type_impl::get_instance(boolean_type, utf8_type, true), column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression sub =
subscript{.val = unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("r", true)},
.sub = make_bool_untyped("true")};
expression prepared = prepare_expression(sub, db, "test_ks", table_schema.get(), nullptr);
expression expected = subscript{
.val = column_value(table_schema->get_column_definition("r")), .sub = make_bool_const(true), .type = utf8_type};
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_subscript_set) {
schema_ptr table_schema =
schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("r", set_type_impl::get_instance(boolean_type, true), column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression sub =
subscript{.val = unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("r", true)},
.sub = make_int_untyped("123")};
BOOST_REQUIRE_THROW(prepare_expression(sub, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_subscript_list_checks_type) {
schema_ptr table_schema =
schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("r", list_type_impl::get_instance(boolean_type, true), column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression sub =
subscript{.val = unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("r", true)},
.sub = make_bool_untyped("true")};
BOOST_REQUIRE_THROW(prepare_expression(sub, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_subscript_map_checks_type) {
schema_ptr table_schema =
schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("r", map_type_impl::get_instance(boolean_type, utf8_type, true), column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression sub =
subscript{.val = unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("r", true)},
.sub = make_int_untyped("123")};
BOOST_REQUIRE_THROW(prepare_expression(sub, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_token) {
schema_ptr table_schema = schema_builder("test_ks", "test_cf")
.with_column("p1", int32_type, column_kind::partition_key)
.with_column("p2", int32_type, column_kind::partition_key)
.with_column("p3", int32_type, column_kind::partition_key)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
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);
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"))};
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);
}
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)
.with_column("p2", int32_type, column_kind::partition_key)
.with_column("p3", int32_type, column_kind::partition_key)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression tok = make_token(std::vector<expression>());
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) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression cast_expr =
cast{.arg = make_int_untyped("123"),
.type = cql3_type::raw::from(int32_type)};
::lw_shared_ptr<column_specification> receiver = make_receiver(int32_type);
expression prepared = prepare_expression(cast_expr, db, "test_ks", table_schema.get(), receiver);
expression expected = cast{.arg = make_int_const(123), .type = int32_type};
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_cast_int_short) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression cast_expr =
cast{.arg = make_int_untyped("123"),
.type = cql3_type::raw::from(short_type)};
::lw_shared_ptr<column_specification> receiver = make_receiver(short_type);
expression prepared = prepare_expression(cast_expr, db, "test_ks", table_schema.get(), receiver);
expression expected = cast{.arg = make_smallint_const(123), .type = short_type};
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_cast_text_int) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression cast_expr =
cast{.arg = make_string_untyped("123"),
.type = cql3_type::raw::from(short_type)};
::lw_shared_ptr<column_specification> receiver = make_receiver(short_type);
BOOST_REQUIRE_THROW(prepare_expression(cast_expr, db, "test_ks", table_schema.get(), receiver),
exceptions::invalid_request_exception);
}
// Test that preparing `(text)?` without a receiver works.
// Here (text) serves as a type hint that specifies the type of the bind variable.
// This syntax is useful for passing bind variables in places where it's impossible
// to infer the type from context, for example as an argument for a function
// with multiple overloads.
BOOST_AUTO_TEST_CASE(prepare_cast_bind_var_no_receiver) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression cast_expr =
cast{.arg = bind_variable{.bind_index = 0, .receiver = nullptr}, .type = cql3_type::raw::from(utf8_type)};
expression prepared = prepare_expression(cast_expr, db, "test_ks", table_schema.get(), nullptr);
// Can't do a direct comparison because we don't have prepared.arg.receiver
BOOST_REQUIRE(is<cast>(prepared) && is<bind_variable>(as<cast>(prepared).arg));
::lw_shared_ptr<column_specification> bind_var_receiver = as<bind_variable>(as<cast>(prepared).arg).receiver;
BOOST_REQUIRE(bind_var_receiver->type == utf8_type);
expression expected = cast{.arg = bind_variable{.bind_index = 0, .receiver = bind_var_receiver}, .type = utf8_type};
BOOST_REQUIRE_EQUAL(prepared, expected);
BOOST_REQUIRE(expr::type_of(prepared) == utf8_type);
}
// Test preparing (text)? with a known text receiver.
// Corresponds to `text_col = (text)?`
BOOST_AUTO_TEST_CASE(prepare_cast_bind_var_text_type_hint_and_text_receiver) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression cast_expr =
cast{.arg = bind_variable{.bind_index = 0, .receiver = nullptr}, .type = cql3_type::raw::from(utf8_type)};
expression prepared = prepare_expression(cast_expr, db, "test_ks", table_schema.get(), make_receiver(utf8_type));
// Can't do a direct comparison because we don't have prepared.arg.receiver
BOOST_REQUIRE(is<cast>(prepared) && is<bind_variable>(as<cast>(prepared).arg));
::lw_shared_ptr<column_specification> bind_var_receiver = as<bind_variable>(as<cast>(prepared).arg).receiver;
BOOST_REQUIRE(bind_var_receiver->type == utf8_type);
expression expected = cast{.arg = bind_variable{.bind_index = 0, .receiver = bind_var_receiver}, .type = utf8_type};
BOOST_REQUIRE_EQUAL(prepared, expected);
BOOST_REQUIRE(expr::type_of(prepared) == utf8_type);
}
// Test preparing (int)? with a known int receiver.
// Corresponds to `int_col = (int)?`
BOOST_AUTO_TEST_CASE(prepare_cast_bind_var_int_type_hint_and_int_receiver) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression cast_expr =
cast{.arg = bind_variable{.bind_index = 0, .receiver = nullptr}, .type = cql3_type::raw::from(int32_type)};
expression prepared = prepare_expression(cast_expr, db, "test_ks", table_schema.get(), make_receiver(int32_type));
// Can't do a direct comparison because we don't have prepared.arg.receiver
BOOST_REQUIRE(is<cast>(prepared) && is<bind_variable>(as<cast>(prepared).arg));
::lw_shared_ptr<column_specification> bind_var_receiver = as<bind_variable>(as<cast>(prepared).arg).receiver;
BOOST_REQUIRE(bind_var_receiver->type == int32_type);
expression expected =
cast{.arg = bind_variable{.bind_index = 0, .receiver = bind_var_receiver}, .type = int32_type};
BOOST_REQUIRE_EQUAL(prepared, expected);
BOOST_REQUIRE(expr::type_of(prepared) == int32_type);
}
// Test preparing (text)? with a known int receiver.
// Corresponds to `int_col = (text)?`
BOOST_AUTO_TEST_CASE(prepare_cast_bind_var_text_type_hint_and_int_receiver) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression cast_expr =
cast{.arg = bind_variable{.bind_index = 0, .receiver = nullptr}, .type = cql3_type::raw::from(utf8_type)};
BOOST_REQUIRE_THROW(prepare_expression(cast_expr, db, "test_ks", table_schema.get(), make_receiver(int32_type)),
exceptions::invalid_request_exception);
}
// Test preparing (int)? with a known text receiver.
// Corresponds to `text_col = (int)?`
BOOST_AUTO_TEST_CASE(prepare_cast_bind_var_int_type_hint_and_text_receiver) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression cast_expr =
cast{.arg = bind_variable{.bind_index = 0, .receiver = nullptr}, .type = cql3_type::raw::from(int32_type)};
BOOST_REQUIRE_THROW(prepare_expression(cast_expr, db, "test_ks", table_schema.get(), make_receiver(utf8_type)),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_null) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression null_expr = make_untyped_null();
expression prepared = prepare_expression(null_expr, db, "test_ks", table_schema.get(), make_receiver(int32_type));
expression expected = constant::make_null(int32_type);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
// null can't be prepared without a receiver because we are unable to infer the type.
BOOST_AUTO_TEST_CASE(prepare_null_no_type_fails) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression null_expr = make_untyped_null();
BOOST_REQUIRE_THROW(prepare_expression(null_expr, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_bind_variable) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression bind_var = bind_variable{.bind_index = 1, .receiver = nullptr};
::lw_shared_ptr<column_specification> receiver = make_receiver(int32_type);
expression prepared = prepare_expression(bind_var, db, "test_ks", table_schema.get(), receiver);
expression expected = bind_variable{
.bind_index = 1,
.receiver = receiver,
};
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_bind_variable_no_receiver) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression bind_var = bind_variable{.bind_index = 1, .receiver = nullptr};
BOOST_REQUIRE_THROW(prepare_expression(bind_var, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_untyped_constant_no_receiver) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression untyped = make_int_untyped("1337");
// Can't infer type
BOOST_REQUIRE_THROW(prepare_expression(untyped, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_untyped_constant_bool) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression untyped = make_bool_untyped("true");
expression prepared = prepare_expression(untyped, db, "test_ks", table_schema.get(), make_receiver(boolean_type));
expression expected = make_bool_const(true);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_untyped_constant_int8) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression untyped = make_int_untyped("13");
expression prepared = prepare_expression(untyped, db, "test_ks", table_schema.get(), make_receiver(byte_type));
expression expected = make_tinyint_const(13);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_untyped_constant_int16) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression untyped = make_int_untyped("1337");
expression prepared = prepare_expression(untyped, db, "test_ks", table_schema.get(), make_receiver(short_type));
expression expected = make_smallint_const(1337);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_untyped_constant_int32) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression untyped =
make_int_untyped("13377331");
expression prepared = prepare_expression(untyped, db, "test_ks", table_schema.get(), make_receiver(int32_type));
expression expected = make_int_const(13377331);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_untyped_constant_int64) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression untyped =
make_int_untyped("1337733113377331");
expression prepared = prepare_expression(untyped, db, "test_ks", table_schema.get(), make_receiver(long_type));
expression expected = make_bigint_const(1337733113377331);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_untyped_constant_text) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression untyped =
make_string_untyped("scylla_is_the_best");
expression prepared = prepare_expression(untyped, db, "test_ks", table_schema.get(), make_receiver(utf8_type));
expression expected = make_text_const("scylla_is_the_best");
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_untyped_constant_bad_int) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression untyped =
make_int_untyped("not_integer_text");
BOOST_REQUIRE_THROW(prepare_expression(untyped, db, "test_ks", table_schema.get(), make_receiver(int32_type)),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_tuple_constructor_no_receiver_fails) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression tup = tuple_constructor{
.elements =
{
make_int_untyped("123"),
make_int_untyped("456"),
make_string_untyped("some text"),
},
.type = nullptr};
BOOST_REQUIRE_THROW(prepare_expression(tup, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_tuple_constructor) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression tup = tuple_constructor{
.elements =
{
make_int_untyped("123"),
make_int_untyped("456"),
make_string_untyped("some text"),
},
.type = nullptr};
data_type tup_type = tuple_type_impl::get_instance({int32_type, short_type, utf8_type});
::lw_shared_ptr<column_specification> receiver = make_receiver(tup_type);
expression prepared = prepare_expression(tup, db, "test_ks", table_schema.get(), receiver);
expression expected =
make_tuple_const({make_int_const(123), make_smallint_const(456), make_text_const("some text")},
{int32_type, short_type, utf8_type});
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_tuple_constructor_of_columns) {
schema_ptr table_schema = schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("c1", int32_type, column_kind::clustering_key)
.with_column("c2", utf8_type, column_kind::clustering_key)
.with_column("c3", byte_type, column_kind::clustering_key)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression tup = tuple_constructor{
.elements = {unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("c1", true)},
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("c2", true)},
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("c3", true)}},
.type = nullptr};
data_type tup_type = tuple_type_impl::get_instance({int32_type, utf8_type, byte_type});
expression prepared = prepare_expression(tup, db, "test_ks", table_schema.get(), nullptr);
expression expected = tuple_constructor{.elements =
{
column_value(table_schema->get_column_definition("c1")),
column_value(table_schema->get_column_definition("c2")),
column_value(table_schema->get_column_definition("c3")),
},
.type = tup_type};
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_list_collection_constructor) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor = collection_constructor{
.style = collection_constructor::style_type::list,
.elements =
{
make_int_untyped("123"),
make_int_untyped("456"),
make_int_untyped("789"),
},
.type = nullptr};
data_type list_type = list_type_impl::get_instance(long_type, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(list_type));
expression expected =
make_list_const({make_bigint_const(123), make_bigint_const(456), make_bigint_const(789)}, long_type);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
// preparing empty nonfrozen collections results in null
BOOST_AUTO_TEST_CASE(prepare_list_collection_constructor_empty_nonfrozen) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor =
collection_constructor{.style = collection_constructor::style_type::list, .elements = {}, .type = nullptr};
data_type list_type = list_type_impl::get_instance(long_type, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(list_type));
expression expected = constant::make_null(list_type);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_list_collection_constructor_empty_frozen) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor =
collection_constructor{.style = collection_constructor::style_type::list, .elements = {}, .type = nullptr};
data_type list_type = list_type_impl::get_instance(long_type, false);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(list_type));
expression expected = constant(make_list_raw({}), list_type);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_list_collection_constructor_no_receiver) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor = collection_constructor{
.style = collection_constructor::style_type::list,
.elements =
{
make_int_untyped("123"),
make_int_untyped("456"),
make_int_untyped("789"),
},
.type = nullptr};
data_type list_type = list_type_impl::get_instance(long_type, true);
BOOST_REQUIRE_THROW(prepare_expression(constructor, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_list_collection_constructor_with_bind_var) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor = collection_constructor{
.style = collection_constructor::style_type::list,
.elements =
{
make_int_untyped("123"),
bind_variable{.bind_index = 1, .receiver = nullptr},
make_int_untyped("789"),
},
.type = nullptr};
data_type list_type = list_type_impl::get_instance(long_type, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(list_type));
// prepared bind_variable contains a receiver which we need to extract
// in order to prepare an equal expected value.
collection_constructor* prepared_constructor = as_if<collection_constructor>(&prepared);
BOOST_REQUIRE(prepared_constructor != nullptr);
BOOST_REQUIRE_EQUAL(prepared_constructor->elements.size(), 3);
bind_variable* prepared_bind_var = as_if<bind_variable>(&prepared_constructor->elements[1]);
BOOST_REQUIRE(prepared_bind_var != nullptr);
::lw_shared_ptr<column_specification> bind_var_receiver = prepared_bind_var->receiver;
BOOST_REQUIRE(bind_var_receiver.get() != nullptr);
BOOST_REQUIRE(bind_var_receiver->type == long_type);
expression expected = collection_constructor{
.style = collection_constructor::style_type::list,
.elements = {make_bigint_const(123), bind_variable{.bind_index = 1, .receiver = bind_var_receiver},
make_bigint_const(789)},
.type = list_type};
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_list_collection_constructor_with_null) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor = collection_constructor{
.style = collection_constructor::style_type::list,
.elements = {make_int_untyped("123"),
make_int_untyped("456"),
make_untyped_null()},
.type = nullptr};
data_type list_type = list_type_impl::get_instance(int32_type, true);
BOOST_REQUIRE_EQUAL(prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(list_type)),
make_int_list_const({123, 456, std::nullopt}));
}
BOOST_AUTO_TEST_CASE(prepare_set_collection_constructor) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor = collection_constructor{
.style = collection_constructor::style_type::set,
.elements =
{
make_int_untyped("789"),
make_int_untyped("123"),
make_int_untyped("456"),
},
.type = nullptr};
data_type set_type = set_type_impl::get_instance(short_type, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(set_type));
expression expected =
make_set_const({make_smallint_const(123), make_smallint_const(456), make_smallint_const(789)}, short_type);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
// preparing empty nonfrozen collections results in null
BOOST_AUTO_TEST_CASE(prepare_set_collection_constructor_empty_nonfrozen) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor =
collection_constructor{.style = collection_constructor::style_type::set, .elements = {}, .type = nullptr};
data_type set_type = set_type_impl::get_instance(short_type, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(set_type));
expression expected = constant::make_null(set_type);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_set_collection_constructor_empty_frozen) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor =
collection_constructor{.style = collection_constructor::style_type::set, .elements = {}, .type = nullptr};
data_type set_type = set_type_impl::get_instance(short_type, false);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(set_type));
expression expected = constant(make_set_raw({}), set_type);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_set_collection_constructor_no_receiver) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor = collection_constructor{
.style = collection_constructor::style_type::set,
.elements =
{
make_int_untyped("789"),
make_int_untyped("123"),
make_int_untyped("456"),
},
.type = nullptr};
BOOST_REQUIRE_THROW(prepare_expression(constructor, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_set_collection_constructor_with_bind_var) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor = collection_constructor{
.style = collection_constructor::style_type::set,
.elements =
{
make_int_untyped("789"),
bind_variable{.bind_index = 1, .receiver = nullptr},
make_int_untyped("123"),
},
.type = nullptr};
data_type set_type = set_type_impl::get_instance(long_type, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(set_type));
// Can't directly compare because the bind variable receiver is created inside prepare_expression
// prepared bind_variable contains a receiver which we need to extract
// in order to prepare an equal expected value.
collection_constructor* prepared_constructor = as_if<collection_constructor>(&prepared);
BOOST_REQUIRE(prepared_constructor != nullptr);
BOOST_REQUIRE_EQUAL(prepared_constructor->elements.size(), 3);
bind_variable* prepared_bind_var = as_if<bind_variable>(&prepared_constructor->elements[1]);
BOOST_REQUIRE(prepared_bind_var != nullptr);
::lw_shared_ptr<column_specification> bind_var_receiver = prepared_bind_var->receiver;
BOOST_REQUIRE(bind_var_receiver.get() != nullptr);
BOOST_REQUIRE(bind_var_receiver->type == long_type);
expression expected = collection_constructor{
.style = collection_constructor::style_type::set,
.elements = {make_bigint_const(789), bind_variable{.bind_index = 1, .receiver = bind_var_receiver},
make_bigint_const(123)},
.type = set_type};
}
BOOST_AUTO_TEST_CASE(prepare_set_collection_constructor_with_null) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor = collection_constructor{
.style = collection_constructor::style_type::set,
.elements =
{
make_int_untyped("789"),
make_untyped_null(),
make_int_untyped("456"),
},
.type = nullptr};
data_type set_type = set_type_impl::get_instance(short_type, true);
BOOST_REQUIRE_THROW(prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(set_type)),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_map_collection_constructor) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor =
collection_constructor{
.style = collection_constructor::style_type::map,
.elements =
{
tuple_constructor{
.elements =
{make_int_untyped("3"),
make_int_untyped("30")},
.type = nullptr},
tuple_constructor{
.elements = {make_int_untyped("2"),
make_int_untyped("-20")},
.type = nullptr},
tuple_constructor{
.elements = {make_int_untyped("1"),
make_int_untyped("10")},
.type = nullptr},
},
.type = nullptr};
data_type map_type = map_type_impl::get_instance(short_type, long_type, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(map_type));
expression expected = make_map_const({{make_smallint_const(1), make_bigint_const(10)},
{make_smallint_const(2), make_bigint_const(-20)},
{make_smallint_const(3), make_bigint_const(30)}},
short_type, long_type);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
// preparing empty nonfrozen collections results in null
BOOST_AUTO_TEST_CASE(prepare_map_collection_constructor_empty_nonfrozen) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor =
collection_constructor{.style = collection_constructor::style_type::map, .elements = {}, .type = nullptr};
data_type map_type = map_type_impl::get_instance(short_type, long_type, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(map_type));
expression expected = constant::make_null(map_type);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_map_collection_constructor_empty_frozen) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor =
collection_constructor{.style = collection_constructor::style_type::map, .elements = {}, .type = nullptr};
data_type map_type = map_type_impl::get_instance(short_type, long_type, false);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(map_type));
expression expected = constant(make_map_raw({}), map_type);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_map_collection_constructor_no_receiver) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor =
collection_constructor{
.style = collection_constructor::style_type::map,
.elements =
{
tuple_constructor{
.elements =
{make_int_untyped("3"),
make_int_untyped("30")},
.type = nullptr},
tuple_constructor{
.elements = {make_int_untyped("2"),
make_int_untyped("-20")},
.type = nullptr},
tuple_constructor{
.elements = {make_int_untyped("1"),
make_int_untyped("10")},
.type = nullptr},
},
.type = nullptr};
BOOST_REQUIRE_THROW(prepare_expression(constructor, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_map_collection_constructor_with_bind_var_key) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor =
collection_constructor{
.style = collection_constructor::style_type::map,
.elements =
{
tuple_constructor{
.elements =
{make_int_untyped("3"),
make_int_untyped("30")},
.type = nullptr},
tuple_constructor{
.elements = {bind_variable{.bind_index = 1, .receiver = nullptr},
make_int_untyped("-20")},
.type = nullptr},
tuple_constructor{
.elements = {make_int_untyped("1"),
make_int_untyped("10")},
.type = nullptr},
},
.type = nullptr};
data_type map_type = map_type_impl::get_instance(short_type, long_type, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(map_type));
// prepared bind_variable contains a receiver which we need to extract
// in order to prepare an equal expected value.
collection_constructor* prepared_constructor = as_if<collection_constructor>(&prepared);
BOOST_REQUIRE(prepared_constructor != nullptr);
BOOST_REQUIRE_EQUAL(prepared_constructor->elements.size(), 3);
tuple_constructor* prepared_tup = as_if<tuple_constructor>(&prepared_constructor->elements[1]);
BOOST_REQUIRE(prepared_tup != nullptr);
BOOST_REQUIRE_EQUAL(prepared_tup->elements.size(), 2);
bind_variable* prepared_bind_var = as_if<bind_variable>(&prepared_tup->elements[0]);
BOOST_REQUIRE(prepared_bind_var != nullptr);
::lw_shared_ptr<column_specification> bind_var_receiver = prepared_bind_var->receiver;
BOOST_REQUIRE(bind_var_receiver->type == short_type);
expression expected = collection_constructor{
.style = collection_constructor::style_type::map,
.elements =
{
tuple_constructor{.elements = {make_smallint_const(3), make_bigint_const(30)},
.type = tuple_type_impl::get_instance({short_type, long_type})},
tuple_constructor{
.elements = {bind_variable{.bind_index = 1, .receiver = bind_var_receiver}, make_bigint_const(-20)},
.type = tuple_type_impl::get_instance({short_type, long_type})},
tuple_constructor{.elements = {make_smallint_const(1), make_bigint_const(10)},
.type = tuple_type_impl::get_instance({short_type, long_type})},
},
.type = map_type};
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_map_collection_constructor_with_bind_var_value) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor = collection_constructor{
.style = collection_constructor::style_type::map,
.elements =
{
tuple_constructor{.elements = {make_int_untyped("3"),
make_int_untyped("30")},
.type = nullptr},
tuple_constructor{.elements = {make_int_untyped("2"),
bind_variable{.bind_index = 1, .receiver = nullptr}},
.type = nullptr},
tuple_constructor{.elements = {make_int_untyped("1"),
make_int_untyped("10")},
.type = nullptr},
},
.type = nullptr};
data_type map_type = map_type_impl::get_instance(short_type, long_type, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(map_type));
// prepared bind_variable contains a receiver which we need to extract
// in order to prepare an equal expected value.
collection_constructor* prepared_constructor = as_if<collection_constructor>(&prepared);
BOOST_REQUIRE(prepared_constructor != nullptr);
BOOST_REQUIRE_EQUAL(prepared_constructor->elements.size(), 3);
tuple_constructor* prepared_tup = as_if<tuple_constructor>(&prepared_constructor->elements[1]);
BOOST_REQUIRE(prepared_tup != nullptr);
BOOST_REQUIRE_EQUAL(prepared_tup->elements.size(), 2);
bind_variable* prepared_bind_var = as_if<bind_variable>(&prepared_tup->elements[1]);
BOOST_REQUIRE(prepared_bind_var != nullptr);
::lw_shared_ptr<column_specification> bind_var_receiver = prepared_bind_var->receiver;
BOOST_REQUIRE(bind_var_receiver->type == long_type);
expression expected = collection_constructor{
.style = collection_constructor::style_type::map,
.elements =
{
tuple_constructor{.elements = {make_smallint_const(3), make_bigint_const(30)},
.type = tuple_type_impl::get_instance({short_type, long_type})},
tuple_constructor{
.elements = {make_smallint_const(2), bind_variable{.bind_index = 1, .receiver = bind_var_receiver}},
.type = tuple_type_impl::get_instance({short_type, long_type})},
tuple_constructor{.elements = {make_smallint_const(1), make_bigint_const(10)},
.type = tuple_type_impl::get_instance({short_type, long_type})},
},
.type = map_type};
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_map_collection_constructor_null_key) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor =
collection_constructor{
.style = collection_constructor::style_type::map,
.elements =
{
tuple_constructor{
.elements =
{make_int_untyped("3"),
make_int_untyped("30")},
.type = nullptr},
tuple_constructor{
.elements = {make_untyped_null(),
make_int_untyped("-20")},
.type = nullptr},
tuple_constructor{
.elements = {make_int_untyped("1"),
make_int_untyped("10")},
.type = nullptr},
},
.type = nullptr};
data_type map_type = map_type_impl::get_instance(short_type, long_type, true);
BOOST_REQUIRE_THROW(prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(map_type)),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_map_collection_constructor_null_value) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression constructor = collection_constructor{
.style = collection_constructor::style_type::map,
.elements =
{
tuple_constructor{.elements = {make_int_untyped("3"),
make_int_untyped("30")},
.type = nullptr},
tuple_constructor{.elements = {make_int_untyped("2"),
make_untyped_null()},
.type = nullptr},
tuple_constructor{.elements = {make_int_untyped("1"),
make_int_untyped("10")},
.type = nullptr},
},
.type = nullptr};
data_type map_type = map_type_impl::get_instance(short_type, long_type, true);
BOOST_REQUIRE_THROW(prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(map_type)),
exceptions::invalid_request_exception);
}
// preparing the collection constructor should check that the type of constructor
// matches the type of the receiver. style_type::set shouldn't be assignable to a list.
BOOST_AUTO_TEST_CASE(prepare_collection_constructor_checks_style_type) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression set_constructor = collection_constructor{
.style = collection_constructor::style_type::set,
.elements = {make_int_untyped("123")},
.type = nullptr};
data_type set_type = set_type_impl::get_instance(int32_type, true);
expression prepared =
prepare_expression(set_constructor, db, "test_ks", table_schema.get(), make_receiver(set_type));
expression expected = make_set_const({make_int_const(123)}, int32_type);
BOOST_REQUIRE_EQUAL(prepared, expected);
data_type list_type = list_type_impl::get_instance(int32_type, true);
BOOST_REQUIRE_THROW(
prepare_expression(set_constructor, db, "test_ks", table_schema.get(), make_receiver(list_type)),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_usertype_constructor) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
usertype_constructor::elements_map_type constructor_elements;
constructor_elements.emplace(
column_identifier("field1", true),
make_int_untyped("152"));
constructor_elements.emplace(
column_identifier("field2", true),
make_int_untyped("987"));
constructor_elements.emplace(
column_identifier("field3", true),
make_string_untyped("ututu"));
expression constructor = usertype_constructor{.elements = constructor_elements, .type = nullptr};
data_type user_type = user_type_impl::get_instance("test_ks", "test_ut", {"field1", "field2", "field3"},
{short_type, long_type, utf8_type}, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(user_type));
raw_value expected_raw = make_tuple_raw({make_smallint_raw(152), make_bigint_raw(987), make_text_raw("ututu")});
expression expected = constant(expected_raw, user_type);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_usertype_constructor_with_null) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
usertype_constructor::elements_map_type constructor_elements;
constructor_elements.emplace(
column_identifier("field1", true),
make_int_untyped("152"));
constructor_elements.emplace(column_identifier("field2", true), make_untyped_null());
constructor_elements.emplace(
column_identifier("field3", true),
make_string_untyped("ututu"));
expression constructor = usertype_constructor{.elements = constructor_elements, .type = nullptr};
data_type user_type = user_type_impl::get_instance("test_ks", "test_ut", {"field1", "field2", "field3"},
{short_type, long_type, utf8_type}, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(user_type));
raw_value expected_raw = make_tuple_raw({make_smallint_raw(152), raw_value::make_null(), make_text_raw("ututu")});
expression expected = constant(expected_raw, user_type);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
// prepare_expression will treat all missing as if they were specified with value null.
BOOST_AUTO_TEST_CASE(prepare_usertype_constructor_missing_field) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
usertype_constructor::elements_map_type constructor_elements;
constructor_elements.emplace(
column_identifier("field1", true),
make_int_untyped("152"));
constructor_elements.emplace(
column_identifier("field3", true),
make_string_untyped("ututu"));
expression constructor = usertype_constructor{.elements = constructor_elements, .type = nullptr};
data_type user_type = user_type_impl::get_instance("test_ks", "test_ut", {"field1", "field2", "field3"},
{short_type, long_type, utf8_type}, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(user_type));
raw_value expected_raw = make_tuple_raw({make_smallint_raw(152), raw_value::make_null(), make_text_raw("ututu")});
expression expected = constant(expected_raw, user_type);
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_usertype_constructor_no_receiver) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
usertype_constructor::elements_map_type constructor_elements;
constructor_elements.emplace(
column_identifier("field1", true),
make_int_untyped("152"));
constructor_elements.emplace(
column_identifier("field2", true),
make_int_untyped("987"));
constructor_elements.emplace(
column_identifier("field3", true),
make_string_untyped("ututu"));
expression constructor = usertype_constructor{.elements = constructor_elements, .type = nullptr};
BOOST_REQUIRE_THROW(prepare_expression(constructor, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(prepare_usertype_constructor_with_bind_variable) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
usertype_constructor::elements_map_type constructor_elements;
constructor_elements.emplace(
column_identifier("field1", true),
make_int_untyped("152"));
constructor_elements.emplace(column_identifier("field2", true),
bind_variable{.bind_index = 2, .receiver = nullptr});
constructor_elements.emplace(
column_identifier("field3", true),
make_string_untyped("ututu"));
expression constructor = usertype_constructor{.elements = constructor_elements, .type = nullptr};
data_type user_type = user_type_impl::get_instance("test_ks", "test_ut", {"field1", "field2", "field3"},
{short_type, long_type, utf8_type}, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(user_type));
// prepared bind_variable contains a receiver which we need to extract
// in order to prepare an equal expected value.
usertype_constructor* prepared_constructor = as_if<usertype_constructor>(&prepared);
BOOST_REQUIRE(prepared_constructor != nullptr);
BOOST_REQUIRE(prepared_constructor->elements.contains(column_identifier("field2", true)));
bind_variable* prepared_bind_var =
as_if<bind_variable>(&prepared_constructor->elements.at(column_identifier("field2", true)));
BOOST_REQUIRE(prepared_bind_var != nullptr);
::lw_shared_ptr<column_specification> bind_var_receiver = prepared_bind_var->receiver;
BOOST_REQUIRE(bind_var_receiver.get() != nullptr);
BOOST_REQUIRE(bind_var_receiver->type == long_type);
usertype_constructor::elements_map_type expected_constructor_elements;
expected_constructor_elements.emplace(column_identifier("field1", true), make_smallint_const(152));
expected_constructor_elements.emplace(column_identifier("field2", true),
bind_variable{.bind_index = 2, .receiver = bind_var_receiver});
expected_constructor_elements.emplace(column_identifier("field3", true), make_text_const("ututu"));
expression expected = usertype_constructor{.elements = expected_constructor_elements, .type = user_type};
BOOST_REQUIRE_EQUAL(prepared, expected);
}
// A combination of a bind variable and a missing field.
// prepare_expression should properly fill in the missing field in this case as well.
BOOST_AUTO_TEST_CASE(prepare_usertype_constructor_with_bind_variable_and_missing_field) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
usertype_constructor::elements_map_type constructor_elements;
constructor_elements.emplace(column_identifier("field2", true),
bind_variable{.bind_index = 2, .receiver = nullptr});
constructor_elements.emplace(
column_identifier("field3", true),
make_string_untyped("ututu"));
expression constructor = usertype_constructor{.elements = constructor_elements, .type = nullptr};
data_type user_type = user_type_impl::get_instance("test_ks", "test_ut", {"field1", "field2", "field3"},
{short_type, long_type, utf8_type}, true);
expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(user_type));
// prepared bind_variable contains a receiver which we need to extract
// in order to prepare an equal expected value.
usertype_constructor* prepared_constructor = as_if<usertype_constructor>(&prepared);
BOOST_REQUIRE(prepared_constructor != nullptr);
BOOST_REQUIRE(prepared_constructor->elements.contains(column_identifier("field2", true)));
bind_variable* prepared_bind_var =
as_if<bind_variable>(&prepared_constructor->elements.at(column_identifier("field2", true)));
BOOST_REQUIRE(prepared_bind_var != nullptr);
::lw_shared_ptr<column_specification> bind_var_receiver = prepared_bind_var->receiver;
BOOST_REQUIRE(bind_var_receiver.get() != nullptr);
BOOST_REQUIRE(bind_var_receiver->type == long_type);
usertype_constructor::elements_map_type expected_constructor_elements;
expected_constructor_elements.emplace(column_identifier("field1", true), constant::make_null(short_type));
expected_constructor_elements.emplace(column_identifier("field2", true),
bind_variable{.bind_index = 2, .receiver = bind_var_receiver});
expected_constructor_elements.emplace(column_identifier("field3", true), make_text_const("ututu"));
expression expected = usertype_constructor{.elements = expected_constructor_elements, .type = user_type};
BOOST_REQUIRE_EQUAL(prepared, expected);
}
BOOST_AUTO_TEST_CASE(prepare_constant_no_receiver) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression int_const = make_int_const(1234);
expression prepared_int_const = prepare_expression(int_const, db, "test_ks", table_schema.get(), nullptr);
BOOST_REQUIRE_EQUAL(int_const, prepared_int_const);
expression text_const = make_text_const("helo");
expression prepared_text_const = prepare_expression(text_const, db, "test_ks", table_schema.get(), nullptr);
BOOST_REQUIRE_EQUAL(text_const, prepared_text_const);
}
BOOST_AUTO_TEST_CASE(prepare_constant_with_receiver) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression int_const = make_int_const(1234);
expression prepared_int_const =
prepare_expression(int_const, db, "test_ks", table_schema.get(), make_receiver(int32_type));
BOOST_REQUIRE_EQUAL(int_const, prepared_int_const);
// Preparing an int32 with int64 receiver fails
BOOST_REQUIRE_THROW(prepare_expression(int_const, db, "test_ks", table_schema.get(), make_receiver(long_type)),
exceptions::invalid_request_exception);
// Preparing an int32 with text receiver fails
BOOST_REQUIRE_THROW(prepare_expression(int_const, db, "test_ks", table_schema.get(), make_receiver(utf8_type)),
exceptions::invalid_request_exception);
// Preparing an int32 with blob receiver works - ints can be implicitly converted to blobs
expression prepared_int_blob =
prepare_expression(int_const, db, "test_ks", table_schema.get(), make_receiver(bytes_type));
expression expected_blob = constant(make_int_const(1234).value, bytes_type);
BOOST_REQUIRE_EQUAL(prepared_int_blob, expected_blob);
}
BOOST_AUTO_TEST_CASE(prepare_writetime_ttl) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
auto prep = [&, db = db] (const expression& e, std::optional<data_type> receiver_type = std::nullopt) {
auto receiver = receiver_type
? make_lw_shared<column_specification>("foo", "bar", make_shared<column_identifier>("ci", false), *receiver_type)
: nullptr;
return prepare_expression(e, db, "test_ks", table_schema.get(), receiver);
};
for (auto kind : {column_mutation_attribute::attribute_kind::ttl, column_mutation_attribute::attribute_kind::writetime}) {
auto expected_type = kind == column_mutation_attribute::attribute_kind::ttl ? int32_type : long_type;
auto ok1 = prep(column_mutation_attribute{kind, make_column("r")});
BOOST_REQUIRE_EQUAL(ok1, (column_mutation_attribute{kind, column_value{&table_schema->regular_column_at(0)}}));
BOOST_REQUIRE(type_of(ok1) == expected_type);
// now try with a receiver
auto ok2 = prep(column_mutation_attribute{kind, make_column("r")}, expected_type);
BOOST_REQUIRE_EQUAL(ok1, (column_mutation_attribute{kind, column_value{&table_schema->regular_column_at(0)}}));
BOOST_REQUIRE(type_of(ok1) == expected_type);
// now try with a receiver of the wrong type
BOOST_REQUIRE_THROW(prep(column_mutation_attribute{kind, make_column("r")}, utf8_type), exceptions::invalid_request_exception);
// Try a partition key component
BOOST_REQUIRE_THROW(prep(column_mutation_attribute{kind, make_column("p")}), exceptions::invalid_request_exception);
BOOST_REQUIRE_THROW(prep(column_mutation_attribute{kind, make_column("c")}), exceptions::invalid_request_exception);
// Try something that isn't a column_value
BOOST_REQUIRE_THROW(prep(column_mutation_attribute{kind, bind_variable{}}), exceptions::invalid_request_exception);
}
}
// Test how evaluating a given binary operator behaves when null is present.
// A binary with null on either side should evaluate to null.
static void test_evaluate_binop_null(oper_t op, expression valid_lhs, expression valid_rhs) {
constant lhs_null_val = constant::make_null(type_of(valid_lhs));
constant rhs_null_val = constant::make_null(type_of(valid_rhs));
expression valid_binop = binary_operator(valid_lhs, op, valid_rhs);
BOOST_REQUIRE(evaluate(valid_binop, evaluation_inputs{}).is_value());
expression binop_lhs_null = binary_operator(lhs_null_val, op, valid_rhs);
BOOST_REQUIRE_EQUAL(evaluate(binop_lhs_null, evaluation_inputs{}), raw_value::make_null());
expression binop_rhs_null = binary_operator(valid_lhs, op, rhs_null_val);
BOOST_REQUIRE_EQUAL(evaluate(binop_rhs_null, evaluation_inputs{}), raw_value::make_null());
expression binop_both_null = binary_operator(lhs_null_val, op, rhs_null_val);
BOOST_REQUIRE_EQUAL(evaluate(binop_both_null, evaluation_inputs{}), raw_value::make_null());
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_eq) {
expression true_eq_binop = binary_operator(make_int_const(1), oper_t::EQ, make_int_const(1));
BOOST_REQUIRE_EQUAL(evaluate(true_eq_binop, evaluation_inputs{}), make_bool_raw(true));
expression false_eq_binop = binary_operator(make_int_const(1), oper_t::EQ, make_int_const(2));
BOOST_REQUIRE_EQUAL(evaluate(false_eq_binop, evaluation_inputs{}), make_bool_raw(false));
expression empty_eq = binary_operator(make_empty_const(int32_type), oper_t::EQ, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(empty_eq, evaluation_inputs{}), make_bool_raw(true));
expression empty_neq = binary_operator(make_int_const(0), oper_t::EQ, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(empty_neq, evaluation_inputs{}), make_bool_raw(false));
test_evaluate_binop_null(oper_t::EQ, make_int_const(123), make_int_const(456));
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_neq) {
expression true_neq_binop = binary_operator(make_int_const(1), oper_t::NEQ, make_int_const(1000));
BOOST_REQUIRE_EQUAL(evaluate(true_neq_binop, evaluation_inputs{}), make_bool_raw(true));
expression false_neq_binop = binary_operator(make_int_const(2), oper_t::NEQ, make_int_const(2));
BOOST_REQUIRE_EQUAL(evaluate(false_neq_binop, evaluation_inputs{}), make_bool_raw(false));
expression empty_neq_empty =
binary_operator(make_empty_const(int32_type), oper_t::NEQ, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(empty_neq_empty, evaluation_inputs{}), make_bool_raw(false));
expression empty_neq_0 = binary_operator(make_empty_const(int32_type), oper_t::NEQ, make_int_const(0));
BOOST_REQUIRE_EQUAL(evaluate(empty_neq_0, evaluation_inputs{}), make_bool_raw(true));
test_evaluate_binop_null(oper_t::NEQ, make_int_const(123), make_int_const(456));
}
static
binary_operator
lwt_binary_operator(expression lhs, oper_t op, expression rhs) {
auto ret = binary_operator(std::move(lhs), op, std::move(rhs));
ret.null_handling = null_handling_style::lwt_nulls;
return ret;
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_eq_neq_lwt_nulls) {
auto one = make_int_const(1);
auto two = make_int_const(2);
auto null = constant::make_null(int32_type);
auto t = make_bool_raw(true);
auto f = make_bool_raw(false);
auto eval = [] (const expression& e) { return evaluate(e, evaluation_inputs{}); };
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(one, oper_t::EQ, two)), f);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(one, oper_t::EQ, one)), t);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(one, oper_t::EQ, null)), f);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(null, oper_t::EQ, two)), f);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(null, oper_t::EQ, null)), t);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(one, oper_t::NEQ, two)), t);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(one, oper_t::NEQ, one)), f);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(one, oper_t::NEQ, null)), t);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(null, oper_t::NEQ, two)), t);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(null, oper_t::NEQ, null)), f);
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_in_lwt_nulls) {
auto int32_list_type = list_type_impl::get_instance(int32_type, true);
auto one = make_int_const(1);
auto two = make_int_const(2);
auto null = constant::make_null(int32_type);
auto list_with_null = collection_constructor{
.style = collection_constructor::style_type::list,
.elements = {one, null},
.type = int32_list_type,
};
auto list_without_null = collection_constructor{
.style = collection_constructor::style_type::list,
.elements = {one},
.type = int32_list_type,
};
auto null_list = constant::make_null(int32_list_type);
auto t = make_bool_raw(true);
auto f = make_bool_raw(false);
auto eval = [] (const expression& e) { return evaluate(e, evaluation_inputs{}); };
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(one, oper_t::IN, list_with_null)), t);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(two, oper_t::IN, list_with_null)), f);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(null, oper_t::IN, list_with_null)), t);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(one, oper_t::IN, list_without_null)), t);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(two, oper_t::IN, list_without_null)), f);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(null, oper_t::IN, list_without_null)), f);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(one, oper_t::IN, null_list)), f);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(two, oper_t::IN, null_list)), f);
BOOST_REQUIRE_EQUAL(eval(lwt_binary_operator(null, oper_t::IN, null_list)), f);
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_lt) {
expression true_lt_binop = binary_operator(make_int_const(1), oper_t::LT, make_int_const(2));
BOOST_REQUIRE_EQUAL(evaluate(true_lt_binop, evaluation_inputs{}), make_bool_raw(true));
expression false_lt_binop = binary_operator(make_int_const(10), oper_t::LT, make_int_const(2));
BOOST_REQUIRE_EQUAL(evaluate(false_lt_binop, evaluation_inputs{}), make_bool_raw(false));
expression false_lt_binop2 = binary_operator(make_int_const(2), oper_t::LT, make_int_const(2));
BOOST_REQUIRE_EQUAL(evaluate(false_lt_binop2, evaluation_inputs{}), make_bool_raw(false));
expression empty_lt_empty = binary_operator(make_empty_const(int32_type), oper_t::LT, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(empty_lt_empty, evaluation_inputs{}), make_bool_raw(false));
expression empty_lt_int_min =
binary_operator(make_empty_const(int32_type), oper_t::LT, make_int_const(std::numeric_limits<int32_t>::min()));
BOOST_REQUIRE_EQUAL(evaluate(empty_lt_int_min, evaluation_inputs{}), make_bool_raw(true));
test_evaluate_binop_null(oper_t::LT, make_int_const(123), make_int_const(456));
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_lte) {
expression true_lte_binop = binary_operator(make_int_const(1), oper_t::LTE, make_int_const(2));
BOOST_REQUIRE_EQUAL(evaluate(true_lte_binop, evaluation_inputs{}), make_bool_raw(true));
expression true_lte_binop2 = binary_operator(make_int_const(12), oper_t::LTE, make_int_const(12));
BOOST_REQUIRE_EQUAL(evaluate(true_lte_binop2, evaluation_inputs{}), make_bool_raw(true));
expression false_lte_binop = binary_operator(make_int_const(123), oper_t::LTE, make_int_const(2));
BOOST_REQUIRE_EQUAL(evaluate(false_lte_binop, evaluation_inputs{}), make_bool_raw(false));
expression empty_lte_empty =
binary_operator(make_empty_const(int32_type), oper_t::LTE, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(empty_lte_empty, evaluation_inputs{}), make_bool_raw(true));
expression empty_lte_int_min =
binary_operator(make_empty_const(int32_type), oper_t::LT, make_int_const(std::numeric_limits<int32_t>::min()));
BOOST_REQUIRE_EQUAL(evaluate(empty_lte_int_min, evaluation_inputs{}), make_bool_raw(true));
test_evaluate_binop_null(oper_t::LTE, make_int_const(123), make_int_const(456));
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_gt) {
expression true_gt_binop = binary_operator(make_int_const(2), oper_t::GT, make_int_const(1));
BOOST_REQUIRE_EQUAL(evaluate(true_gt_binop, evaluation_inputs{}), make_bool_raw(true));
expression false_gt_binop = binary_operator(make_int_const(1), oper_t::GT, make_int_const(2));
BOOST_REQUIRE_EQUAL(evaluate(false_gt_binop, evaluation_inputs{}), make_bool_raw(false));
expression false_gt_binop2 = binary_operator(make_int_const(2), oper_t::GT, make_int_const(2));
BOOST_REQUIRE_EQUAL(evaluate(false_gt_binop2, evaluation_inputs{}), make_bool_raw(false));
expression empty_gt_empty = binary_operator(make_empty_const(int32_type), oper_t::GT, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(empty_gt_empty, evaluation_inputs{}), make_bool_raw(false));
expression int_min_gt_empty =
binary_operator(make_int_const(std::numeric_limits<int32_t>::min()), oper_t::GT, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(int_min_gt_empty, evaluation_inputs{}), make_bool_raw(true));
test_evaluate_binop_null(oper_t::GT, make_int_const(234), make_int_const(-3434));
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_gte) {
expression true_gte_binop = binary_operator(make_int_const(20), oper_t::GTE, make_int_const(10));
BOOST_REQUIRE_EQUAL(evaluate(true_gte_binop, evaluation_inputs{}), make_bool_raw(true));
expression true_gte_binop2 = binary_operator(make_int_const(10), oper_t::GTE, make_int_const(10));
BOOST_REQUIRE_EQUAL(evaluate(true_gte_binop2, evaluation_inputs{}), make_bool_raw(true));
expression false_gte_binop = binary_operator(make_int_const(-10), oper_t::GTE, make_int_const(10));
BOOST_REQUIRE_EQUAL(evaluate(false_gte_binop, evaluation_inputs{}), make_bool_raw(false));
expression empty_gte_empty =
binary_operator(make_empty_const(int32_type), oper_t::GTE, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(empty_gte_empty, evaluation_inputs{}), make_bool_raw(true));
expression int_min_gte_empty =
binary_operator(make_int_const(std::numeric_limits<int32_t>::min()), oper_t::GTE, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(int_min_gte_empty, evaluation_inputs{}), make_bool_raw(true));
test_evaluate_binop_null(oper_t::GTE, make_int_const(234), make_int_const(-3434));
}
// helper to evaluate expressions that we expect to throw sometimes. std::nullopt -> threw
static
std::optional<raw_value>
evaluate_maybe_exception(const expression& e, const evaluation_inputs& inputs) {
try {
return evaluate(e, evaluation_inputs{});
} catch (exceptions::invalid_request_exception&) {
return std::nullopt;
}
}
// LWT inequality has different results if the second operand is NULL
BOOST_AUTO_TEST_CASE(evalute_lwt_inequality) {
const auto operand_pool = std::vector({make_int_const(2), make_int_const(3), constant::make_null(int32_type)});
for (auto op : { oper_t::LT, oper_t::LTE, oper_t::GT, oper_t::GTE}) {
for (auto& lhs : operand_pool) {
for (auto& rhs: operand_pool) {
auto binop = lwt_binary_operator(lhs, op, rhs);
auto result = evaluate_maybe_exception(binop, evaluation_inputs{});
if (rhs.is_null()) {
BOOST_REQUIRE(!result); // NULL rhs is illegal for lwt
} else {
// Otherwise, results should be the same as for non-LWT, except FALSE is returned
// instead of NULL
binop.null_handling = null_handling_style::sql;
auto sql_result = evaluate_maybe_exception(binop, evaluation_inputs{});
if (sql_result && sql_result->is_null()) {
sql_result = cql3::raw_value::make_value(data_value(false).serialize_nonnull());
}
BOOST_REQUIRE_EQUAL(result, sql_result);
}
}
}
}
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_in) {
// IN expects a list as its rhs, sets are not allowed
expression in_list = make_int_list_const({1, 3, 5});
expression true_in_binop = binary_operator(make_int_const(3), oper_t::IN, in_list);
BOOST_REQUIRE_EQUAL(evaluate(true_in_binop, evaluation_inputs{}), make_bool_raw(true));
expression false_in_binop = binary_operator(make_int_const(2), oper_t::IN, in_list);
BOOST_REQUIRE_EQUAL(evaluate(false_in_binop, evaluation_inputs{}), make_bool_raw(false));
expression empty_in_list = binary_operator(make_empty_const(int32_type), oper_t::IN, in_list);
BOOST_REQUIRE_EQUAL(evaluate(empty_in_list, evaluation_inputs{}), make_bool_raw(false));
expression list_with_empty =
make_list_const({make_int_const(1), make_empty_const(int32_type), make_int_const(3)}, int32_type);
expression empty_in_list_with_empty = binary_operator(make_empty_const(int32_type), oper_t::IN, list_with_empty);
BOOST_REQUIRE_EQUAL(evaluate(empty_in_list_with_empty, evaluation_inputs{}), make_bool_raw(true));
expression existing_int_in_list_with_empty = binary_operator(make_int_const(3), oper_t::IN, list_with_empty);
BOOST_REQUIRE_EQUAL(evaluate(existing_int_in_list_with_empty, evaluation_inputs{}), make_bool_raw(true));
expression nonexisting_int_in_list_with_empty = binary_operator(make_int_const(321), oper_t::IN, list_with_empty);
BOOST_REQUIRE_EQUAL(evaluate(nonexisting_int_in_list_with_empty, evaluation_inputs{}), make_bool_raw(false));
test_evaluate_binop_null(oper_t::IN, make_int_const(5), in_list);
}
// Tests `<int_value> IN (123, ?, 789)` where the bind variable has value 456
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_in_list_with_bind_variable) {
schema_ptr table_schema =
schema_builder("test_ks", "test_cf").with_column("pk", int32_type, column_kind::partition_key).build();
expression in_list = collection_constructor{
.style = collection_constructor::style_type::list,
.elements = {make_int_const(123), bind_variable{.bind_index = 0, .receiver = make_receiver(int32_type)},
make_int_const(789)},
.type = list_type_impl::get_instance(int32_type, true)};
auto [inputs, inputs_data] = make_evaluation_inputs(table_schema, {{"pk", make_int_raw(111)}}, {make_int_raw(456)});
expression true_in_binop = binary_operator(make_int_const(456), oper_t::IN, in_list);
BOOST_REQUIRE_EQUAL(evaluate(true_in_binop, inputs), make_bool_raw(true));
expression false_in_binop = binary_operator(make_int_const(-100), oper_t::IN, in_list);
BOOST_REQUIRE_EQUAL(evaluate(false_in_binop, inputs), make_bool_raw(false));
expression empty_in_list = binary_operator(make_empty_const(int32_type), oper_t::IN, in_list);
BOOST_REQUIRE_EQUAL(evaluate(empty_in_list, inputs), make_bool_raw(false));
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_list_contains) {
expression list_val = make_int_list_const({1, 3, 5});
expression list_contains_true = binary_operator(list_val, oper_t::CONTAINS, make_int_const(3));
BOOST_REQUIRE_EQUAL(evaluate(list_contains_true, evaluation_inputs{}), make_bool_raw(true));
expression list_contains_false = binary_operator(list_val, oper_t::CONTAINS, make_int_const(2));
BOOST_REQUIRE_EQUAL(evaluate(list_contains_false, evaluation_inputs{}), make_bool_raw(false));
expression list_contains_empty = binary_operator(list_val, oper_t::CONTAINS, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(list_contains_empty, evaluation_inputs{}), make_bool_raw(false));
expression list_with_empty =
make_list_const({make_int_const(1), make_empty_const(int32_type), make_int_const(3)}, int32_type);
expression list_with_empty_contains_empty =
binary_operator(list_with_empty, oper_t::CONTAINS, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(list_with_empty_contains_empty, evaluation_inputs{}), make_bool_raw(true));
expression list_with_empty_contains_existing_int =
binary_operator(list_with_empty, oper_t::CONTAINS, make_int_const(3));
BOOST_REQUIRE_EQUAL(evaluate(list_with_empty_contains_existing_int, evaluation_inputs{}), make_bool_raw(true));
expression list_with_empty_contains_nonexisting_int =
binary_operator(list_with_empty, oper_t::CONTAINS, make_int_const(321));
BOOST_REQUIRE_EQUAL(evaluate(list_with_empty_contains_nonexisting_int, evaluation_inputs{}), make_bool_raw(false));
test_evaluate_binop_null(oper_t::CONTAINS, list_val, make_int_const(5));
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_set_contains) {
expression set_val = make_int_set_const({1, 3, 5});
expression set_contains_true = binary_operator(set_val, oper_t::CONTAINS, make_int_const(3));
BOOST_REQUIRE_EQUAL(evaluate(set_contains_true, evaluation_inputs{}), make_bool_raw(true));
expression set_contains_false = binary_operator(set_val, oper_t::CONTAINS, make_int_const(2));
BOOST_REQUIRE_EQUAL(evaluate(set_contains_false, evaluation_inputs{}), make_bool_raw(false));
expression set_contains_empty = binary_operator(set_val, oper_t::CONTAINS, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(set_contains_empty, evaluation_inputs{}), make_bool_raw(false));
expression set_with_empty =
make_set_const({make_empty_const(int32_type), make_int_const(2), make_int_const(3)}, int32_type);
expression set_with_empty_contains_empty =
binary_operator(set_with_empty, oper_t::CONTAINS, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(set_with_empty_contains_empty, evaluation_inputs{}), make_bool_raw(true));
expression set_with_empty_contains_existing_int =
binary_operator(set_with_empty, oper_t::CONTAINS, make_int_const(3));
BOOST_REQUIRE_EQUAL(evaluate(set_with_empty_contains_existing_int, evaluation_inputs{}), make_bool_raw(true));
expression set_with_empty_contains_nonexisting_int =
binary_operator(set_with_empty, oper_t::CONTAINS, make_int_const(321));
BOOST_REQUIRE_EQUAL(evaluate(set_with_empty_contains_nonexisting_int, evaluation_inputs{}), make_bool_raw(false));
test_evaluate_binop_null(oper_t::CONTAINS, set_val, make_int_const(5));
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_map_contains) {
expression map_val = make_int_int_map_const({{1, 2}, {3, 4}, {5, 6}});
expression map_contains_true = binary_operator(map_val, oper_t::CONTAINS, make_int_const(4));
BOOST_REQUIRE_EQUAL(evaluate(map_contains_true, evaluation_inputs{}), make_bool_raw(true));
expression map_contains_false = binary_operator(map_val, oper_t::CONTAINS, make_int_const(3));
BOOST_REQUIRE_EQUAL(evaluate(map_contains_false, evaluation_inputs{}), make_bool_raw(false));
expression map_contains_empty = binary_operator(map_val, oper_t::CONTAINS, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(map_contains_empty, evaluation_inputs{}), make_bool_raw(false));
expression map_with_empty =
make_map_const({{make_int_const(1), make_empty_const(int32_type)}, {make_int_const(3), make_int_const(4)}},
int32_type, int32_type);
expression map_with_empty_contains_empty =
binary_operator(map_with_empty, oper_t::CONTAINS, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(map_with_empty_contains_empty, evaluation_inputs{}), make_bool_raw(true));
expression map_with_empty_contains_existing_int =
binary_operator(map_with_empty, oper_t::CONTAINS, make_int_const(4));
BOOST_REQUIRE_EQUAL(evaluate(map_with_empty_contains_existing_int, evaluation_inputs{}), make_bool_raw(true));
expression map_with_empty_contains_nonexisting_int =
binary_operator(map_with_empty, oper_t::CONTAINS, make_int_const(3));
BOOST_REQUIRE_EQUAL(evaluate(map_with_empty_contains_nonexisting_int, evaluation_inputs{}), make_bool_raw(false));
test_evaluate_binop_null(oper_t::CONTAINS, map_val, make_int_const(5));
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_map_contains_key) {
expression map_val = make_int_int_map_const({{1, 2}, {3, 4}, {5, 6}});
expression true_contains_key_binop = binary_operator(map_val, oper_t::CONTAINS_KEY, make_int_const(5));
BOOST_REQUIRE_EQUAL(evaluate(true_contains_key_binop, evaluation_inputs{}), make_bool_raw(true));
expression false_contains_key_binop = binary_operator(map_val, oper_t::CONTAINS_KEY, make_int_const(6));
BOOST_REQUIRE_EQUAL(evaluate(false_contains_key_binop, evaluation_inputs{}), make_bool_raw(false));
expression map_contains_key_empty = binary_operator(map_val, oper_t::CONTAINS_KEY, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(map_contains_key_empty, evaluation_inputs{}), make_bool_raw(false));
expression map_with_empty =
make_map_const({{make_empty_const(int32_type), make_int_const(2)}, {make_int_const(3), make_int_const(4)}},
int32_type, int32_type);
expression map_with_empty_contains_key_empty =
binary_operator(map_with_empty, oper_t::CONTAINS_KEY, make_empty_const(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(map_with_empty_contains_key_empty, evaluation_inputs{}), make_bool_raw(true));
expression map_with_empty_contains_key_existing_int =
binary_operator(map_with_empty, oper_t::CONTAINS_KEY, make_int_const(3));
BOOST_REQUIRE_EQUAL(evaluate(map_with_empty_contains_key_existing_int, evaluation_inputs{}), make_bool_raw(true));
expression map_with_empty_contains_key_nonexisting_int =
binary_operator(map_with_empty, oper_t::CONTAINS_KEY, make_int_const(4));
BOOST_REQUIRE_EQUAL(evaluate(map_with_empty_contains_key_nonexisting_int, evaluation_inputs{}),
make_bool_raw(false));
test_evaluate_binop_null(oper_t::CONTAINS_KEY, map_val, make_int_const(5));
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_is_not) {
expression true_is_not_binop = binary_operator(make_int_const(1), oper_t::IS_NOT, constant::make_null(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(true_is_not_binop, evaluation_inputs{}), make_bool_raw(true));
expression false_is_not_binop =
binary_operator(constant::make_null(int32_type), oper_t::IS_NOT, constant::make_null(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(false_is_not_binop, evaluation_inputs{}), make_bool_raw(false));
expression forbidden_is_not_binop = binary_operator(make_int_const(1), oper_t::IS_NOT, make_int_const(2));
BOOST_REQUIRE_THROW(evaluate(forbidden_is_not_binop, evaluation_inputs{}), exceptions::invalid_request_exception);
expression empty_is_not_null =
binary_operator(make_empty_const(int32_type), oper_t::IS_NOT, constant::make_null(int32_type));
BOOST_REQUIRE_EQUAL(evaluate(empty_is_not_null, evaluation_inputs{}), make_bool_raw(true));
}
BOOST_AUTO_TEST_CASE(evaluate_binary_operator_like) {
expression true_like_binop = binary_operator(make_text_const("some_text"), oper_t::LIKE, make_text_const("some_%"));
BOOST_REQUIRE_EQUAL(evaluate(true_like_binop, evaluation_inputs{}), make_bool_raw(true));
expression false_like_binop =
binary_operator(make_text_const("some_text"), oper_t::LIKE, make_text_const("some_other_%"));
BOOST_REQUIRE_EQUAL(evaluate(false_like_binop, evaluation_inputs{}), make_bool_raw(false));
// Binary representation of an empty value is the same as empty string
BOOST_REQUIRE_EQUAL(make_text_raw(""), make_empty_raw());
expression empty_like_text = binary_operator(make_empty_const(utf8_type), oper_t::LIKE, make_text_const("%"));
BOOST_REQUIRE_EQUAL(evaluate(empty_like_text, evaluation_inputs{}), make_bool_raw(true));
expression text_like_empty = binary_operator(make_text_const(""), oper_t::LIKE, make_empty_const(utf8_type));
BOOST_REQUIRE_EQUAL(evaluate(text_like_empty, evaluation_inputs{}), make_bool_raw(true));
expression empty_like_empty =
binary_operator(make_empty_const(utf8_type), oper_t::LIKE, make_empty_const(utf8_type));
BOOST_REQUIRE_EQUAL(evaluate(empty_like_empty, evaluation_inputs{}), make_bool_raw(true));
test_evaluate_binop_null(oper_t::LIKE, make_text_const("some_text"), make_text_const("some_%"));
}
// An empty conjunction should evaluate to true
BOOST_AUTO_TEST_CASE(evaluate_conjunction_empty) {
expression empty_conj = conjunction{.children = {}};
BOOST_REQUIRE_EQUAL(evaluate(empty_conj, evaluation_inputs{}), make_bool_raw(true));
}
// A conjunction with one false value should evaluate to false
BOOST_AUTO_TEST_CASE(evaluate_conjunction_one_false) {
expression conj_one_false = conjunction{.children = {make_bool_const(false)}};
BOOST_REQUIRE_EQUAL(evaluate(conj_one_false, evaluation_inputs{}), make_bool_raw(false));
}
// A conjunction with one true value should evaluate to true
BOOST_AUTO_TEST_CASE(evaluate_conjunction_one_true) {
expression conj_one_true = conjunction{.children = {make_bool_const(true)}};
BOOST_REQUIRE_EQUAL(evaluate(conj_one_true, evaluation_inputs{}), make_bool_raw(true));
}
// Helper function that evaluates a conjunction with given elements
raw_value eval_conj(std::vector<raw_value> elements) {
std::vector<expression> conj_children;
for (const raw_value& element : elements) {
conj_children.push_back(constant(element, boolean_type));
}
return evaluate(conjunction{.children = conj_children}, evaluation_inputs{});
};
// Evaluate all possible two-element conjunctions containing true, false and null.
BOOST_AUTO_TEST_CASE(evaluate_conjunction_two_elements) {
raw_value true_val = make_bool_raw(true);
raw_value false_val = make_bool_raw(false);
raw_value null_val = raw_value::make_null();
BOOST_REQUIRE_EQUAL(eval_conj({true_val, true_val}), true_val);
BOOST_REQUIRE_EQUAL(eval_conj({true_val, false_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({true_val, null_val}), null_val);
BOOST_REQUIRE_EQUAL(eval_conj({false_val, true_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({false_val, false_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({false_val, null_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({null_val, true_val}), null_val);
BOOST_REQUIRE_EQUAL(eval_conj({null_val, false_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({null_val, null_val}), null_val);
}
// Evaluate all possible three-element conjunctions containing true, false and null.
BOOST_AUTO_TEST_CASE(evaluate_conjunction_three_elements) {
raw_value true_val = make_bool_raw(true);
raw_value false_val = make_bool_raw(false);
raw_value null_val = raw_value::make_null();
BOOST_REQUIRE_EQUAL(eval_conj({true_val, true_val, true_val}), true_val);
BOOST_REQUIRE_EQUAL(eval_conj({true_val, true_val, false_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({true_val, true_val, null_val}), null_val);
BOOST_REQUIRE_EQUAL(eval_conj({true_val, false_val, true_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({true_val, false_val, false_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({true_val, false_val, null_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({true_val, null_val, true_val}), null_val);
BOOST_REQUIRE_EQUAL(eval_conj({true_val, null_val, false_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({true_val, null_val, null_val}), null_val);
BOOST_REQUIRE_EQUAL(eval_conj({false_val, true_val, true_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({false_val, true_val, false_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({false_val, true_val, null_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({false_val, false_val, true_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({false_val, false_val, false_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({false_val, false_val, null_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({false_val, null_val, true_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({false_val, null_val, false_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({false_val, null_val, null_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({null_val, true_val, true_val}), null_val);
BOOST_REQUIRE_EQUAL(eval_conj({null_val, true_val, false_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({null_val, true_val, null_val}), null_val);
BOOST_REQUIRE_EQUAL(eval_conj({null_val, false_val, true_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({null_val, false_val, false_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({null_val, false_val, null_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({null_val, null_val, true_val}), null_val);
BOOST_REQUIRE_EQUAL(eval_conj({null_val, null_val, false_val}), false_val);
BOOST_REQUIRE_EQUAL(eval_conj({null_val, null_val, null_val}), null_val);
}
// `true AND true AND true AND ...' should evaluate to true
BOOST_AUTO_TEST_CASE(evaluate_conjunction_long_true) {
expression conj_long_true = conjunction{
.children = {make_bool_const(true), make_bool_const(true), make_bool_const(true), make_bool_const(true),
make_bool_const(true), make_bool_const(true), make_bool_const(true), make_bool_const(true),
make_bool_const(true), make_bool_const(true), make_bool_const(true), make_bool_const(true),
make_bool_const(true), make_bool_const(true), make_bool_const(true)}};
BOOST_REQUIRE_EQUAL(evaluate(conj_long_true, evaluation_inputs{}), make_bool_raw(true));
}
// `true AND true AND false AND ...' should evaluate to false
BOOST_AUTO_TEST_CASE(evaluate_conjunction_long_mixed) {
expression conj_long_mixed = conjunction{
.children = {make_bool_const(true), make_bool_const(true), make_bool_const(false), make_bool_const(true),
make_bool_const(true), make_bool_const(true), make_bool_const(true), make_bool_const(true),
make_bool_const(false), make_bool_const(false), make_bool_const(true), make_bool_const(false),
make_bool_const(true), make_bool_const(true), make_bool_const(true)}};
BOOST_REQUIRE_EQUAL(evaluate(conj_long_mixed, evaluation_inputs{}), make_bool_raw(false));
}
// A conjunction with one null value should evaluate to null
BOOST_AUTO_TEST_CASE(evaluate_conjunction_one_null) {
expression conj_one_null = conjunction{.children = {constant::make_null(boolean_type)}};
BOOST_REQUIRE_EQUAL(evaluate(conj_one_null, evaluation_inputs{}), raw_value::make_null());
}
// 'true AND true AND true AND null AND true AND ...' should evaluate to null
BOOST_AUTO_TEST_CASE(evaluate_conjunction_with_null) {
expression conj_with_null =
conjunction{.children = {make_bool_const(true), make_bool_const(true), make_bool_const(true),
constant::make_null(boolean_type), make_bool_const(true), make_bool_const(true)}};
BOOST_REQUIRE_EQUAL(evaluate(conj_with_null, evaluation_inputs{}), raw_value::make_null());
}
// Evaluating a conjunction that contains a single empty value should throw an error
BOOST_AUTO_TEST_CASE(evaluate_conjunction_one_empty) {
expression conj_one_empty = conjunction{.children = {make_empty_const(boolean_type)}};
BOOST_REQUIRE_THROW(evaluate(conj_one_empty, evaluation_inputs{}), exceptions::invalid_request_exception);
}
// Evaluating 'true AND true AND true AND EMPTY AND ...' should throw an error
BOOST_AUTO_TEST_CASE(evaluate_conjunction_with_empty) {
expression conj_with_empty =
conjunction{.children = {make_bool_const(true), make_bool_const(true), make_bool_const(true),
make_empty_const(boolean_type), make_bool_const(false), make_bool_const(true)}};
BOOST_REQUIRE_THROW(evaluate(conj_with_empty, evaluation_inputs{}), exceptions::invalid_request_exception);
}
static cql3::query_options query_options_with_unset_bind_variable() {
return cql3::query_options(cql3::raw_value_vector_with_unset({cql3::raw_value::make_null()}, {true}));
}
// Short circuiting on false ignores all further values, even though they could make the expression invalid
BOOST_AUTO_TEST_CASE(evaluate_conjunction_short_circuit_on_false_does_not_detect_invalid_values) {
auto qo = query_options_with_unset_bind_variable();
auto inputs = evaluation_inputs{.options = &qo};
// An expression which would throw an error when evaluated
expression invalid_to_evaluate = conjunction{.children = {new_bind_variable(0)}};
BOOST_REQUIRE_THROW(evaluate(invalid_to_evaluate, inputs), exceptions::invalid_request_exception);
expression conj_with_false_then_invalid =
conjunction{.children = {make_bool_const(true), make_bool_const(false), make_empty_const(boolean_type),
new_bind_variable(0), invalid_to_evaluate, make_bool_const(true)}};
BOOST_REQUIRE_EQUAL(evaluate(conj_with_false_then_invalid, inputs), make_bool_raw(false));
}
// Null doesn't short-circuit
BOOST_AUTO_TEST_CASE(evaluate_conjunction_doesnt_short_circuit_on_null) {
auto qo = query_options_with_unset_bind_variable();
auto inputs = evaluation_inputs{.options = &qo};
// An expression which would throw an error when evaluated
expression invalid_to_evaluate = conjunction{.children = {new_bind_variable(0)}};
BOOST_REQUIRE_THROW(evaluate(invalid_to_evaluate, inputs), exceptions::invalid_request_exception);
expression conj_with_null_then_invalid = conjunction{
.children = {make_bool_const(true), constant::make_null(boolean_type), make_empty_const(boolean_type),
new_bind_variable(0), invalid_to_evaluate, make_bool_const(true)}};
BOOST_REQUIRE_THROW(evaluate(conj_with_null_then_invalid, inputs),
exceptions::invalid_request_exception);
}
// '() AND (true AND true) AND (true AND true) AND (true)' evaluates to true.
// This test also ensures that evaluate(conjunction) calls evaluate
// for each of the conjunction's children.
BOOST_AUTO_TEST_CASE(evaluate_conjunction_of_conjunctions_to_true) {
expression conj1 = conjunction{.children = {}};
expression conj2 = conjunction{.children = {make_bool_const(true), make_bool_const(true)}};
expression conj3 = conjunction{.children = {make_bool_const(true), make_bool_const(true)}};
expression conj4 = conjunction{.children = {make_bool_const(true)}};
expression conj_of_conjs = conjunction{.children = {conj1, conj2, conj3, conj4}};
BOOST_REQUIRE_EQUAL(evaluate(conj_of_conjs, evaluation_inputs{}), make_bool_raw(true));
}
// '() AND (true AND true) AND (true AND false) AND (true)' evaluates to false
BOOST_AUTO_TEST_CASE(evaluate_conjunction_of_conjunctions_to_false) {
expression conj1 = conjunction{.children = {}};
expression conj2 = conjunction{.children = {make_bool_const(true), make_bool_const(true)}};
expression conj3 = conjunction{.children = {make_bool_const(true), make_bool_const(false)}};
expression conj4 = conjunction{.children = {make_bool_const(true)}};
expression conj_of_conjs = conjunction{.children = {conj1, conj2, conj3, conj4}};
BOOST_REQUIRE_EQUAL(evaluate(conj_of_conjs, evaluation_inputs{}), make_bool_raw(false));
}
// '() AND (true AND true) AND (true AND null) AND (false)' evaluates to false
BOOST_AUTO_TEST_CASE(evaluate_conjunction_of_conjunctions_to_null) {
expression conj1 = conjunction{.children = {}};
expression conj2 = conjunction{.children = {make_bool_const(true), make_bool_const(true)}};
expression conj3 = conjunction{.children = {make_bool_const(true), constant::make_null(boolean_type)}};
expression conj4 = conjunction{.children = {make_bool_const(false)}};
expression conj_of_conjs = conjunction{.children = {conj1, conj2, conj3, conj4}};
BOOST_REQUIRE_EQUAL(evaluate(conj_of_conjs, evaluation_inputs{}), make_bool_raw(false));
}
// Evaluating '() AND (true AND true) AND (true AND UNSET_VALUE) AND (false)' throws an error
BOOST_AUTO_TEST_CASE(evaluate_conjunction_of_conjunctions_with_invalid) {
auto qo = query_options_with_unset_bind_variable();
auto inputs = evaluation_inputs{.options = &qo};
expression conj1 = conjunction{.children = {}};
expression conj2 = conjunction{.children = {make_bool_const(true), new_bind_variable(0)}};
expression conj3 = conjunction{.children = {make_bool_const(true), make_bool_const(true)}};
expression conj4 = conjunction{.children = {make_bool_const(true)}};
expression conj_of_conjs = conjunction{.children = {conj1, conj2, conj3, conj4}};
BOOST_REQUIRE_THROW(evaluate(conj_of_conjs, inputs), exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(evaluate_field_selection) {
auto schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(schema);
// The user defined type has 5 fields:
// CREATE TYPE test_ks.my_type (
// int_field int,
// float_field float,
// text_field text,
// bigint_field bigint,
// int_field2 int,
// )
shared_ptr<const user_type_impl> udt_type = user_type_impl::get_instance(
"test_ks", "my_type", {"int_field", "float_field", "text_field", "bigint_field", "int_field2"},
{int32_type, float_type, utf8_type, long_type, int32_type}, true);
// Create a UDT value:
// {int_field: 123, float_field: null, text_field: 'abcdef', bigint_field: blobasbigint(0x), int_field2: 1337}
usertype_constructor::elements_map_type udt_value_elements;
udt_value_elements.emplace(column_identifier("int_field", false), make_int_const(123));
udt_value_elements.emplace(column_identifier("float_field", false), constant::make_null(float_type));
udt_value_elements.emplace(column_identifier("text_field", false), make_text_const("abcdef"));
udt_value_elements.emplace(column_identifier("bigint_field", false), make_empty_const(long_type));
udt_value_elements.emplace(column_identifier("int_field2", false), make_int_const(1337));
expression udt_value =
constant(evaluate(usertype_constructor{.elements = std::move(udt_value_elements), .type = udt_type},
evaluation_inputs{}),
udt_type);
auto make_field_selection = [&](expression value, const char* selected_field, data_type field_type) -> expression {
return field_selection{
.structure = value, .field = make_shared<column_identifier_raw>(selected_field, true), .type = field_type};
};
auto prepare_and_evaluate = [&,db = db] (const expression& e, const evaluation_inputs& inputs) {
auto prepared = prepare_expression(e, db, "", schema.get(), nullptr);
return evaluate(prepared, inputs);
};
// Evaluate the fields, check that field values are correct
BOOST_REQUIRE_EQUAL(prepare_and_evaluate(make_field_selection(udt_value, "int_field", int32_type), evaluation_inputs{}), make_int_raw(123));
BOOST_REQUIRE_EQUAL(prepare_and_evaluate(make_field_selection(udt_value, "float_field", float_type), evaluation_inputs{}),
cql3::raw_value::make_null());
BOOST_REQUIRE_EQUAL(prepare_and_evaluate(make_field_selection(udt_value, "text_field", utf8_type), evaluation_inputs{}),
make_text_raw("abcdef"));
BOOST_REQUIRE_EQUAL(prepare_and_evaluate(make_field_selection(udt_value, "bigint_field", long_type), evaluation_inputs{}),
make_empty_raw());
BOOST_REQUIRE_EQUAL(prepare_and_evaluate(make_field_selection(udt_value, "int_field2", int32_type), evaluation_inputs{}),
make_int_raw(1337));
// Evaluate a nonexistent field, should throw an exception
BOOST_REQUIRE_THROW(prepare_and_evaluate(make_field_selection(udt_value, "field_testing", int32_type), evaluation_inputs{}),
exceptions::invalid_request_exception);
// Create a UDT value with values for the first 3 fields.
// There's no value for the 4th and 5th field, so they should be NULL.
// This is normal behavior, some fields might not have a value if the UDT value was serialized before
// adding new fields to the type.
// {int_field: 123, float_field: null, text_field: ''}
expression short_udt_value =
constant(make_tuple_raw({make_int_raw(123), cql3::raw_value::make_null(), make_text_raw("")}), udt_type);
// Evaluate the first 3 fields, check that the value is correct
BOOST_REQUIRE_EQUAL(prepare_and_evaluate(make_field_selection(short_udt_value, "int_field", int32_type), evaluation_inputs{}),
make_int_raw(123));
BOOST_REQUIRE_EQUAL(prepare_and_evaluate(make_field_selection(short_udt_value, "float_field", float_type), evaluation_inputs{}),
cql3::raw_value::make_null());
BOOST_REQUIRE_EQUAL(prepare_and_evaluate(make_field_selection(short_udt_value, "text_field", utf8_type), evaluation_inputs{}),
make_text_raw(""));
// The serialized value doesn't contain any data for the 4th or 5th field, so they should be NULL.
BOOST_REQUIRE_EQUAL(prepare_and_evaluate(make_field_selection(short_udt_value, "bigint_field", long_type), evaluation_inputs{}),
cql3::raw_value::make_null());
BOOST_REQUIRE_EQUAL(prepare_and_evaluate(make_field_selection(short_udt_value, "int_field2", int32_type), evaluation_inputs{}),
cql3::raw_value::make_null());
// Evaluate a nonexistent field, should throw an exception
BOOST_REQUIRE_THROW(prepare_and_evaluate(make_field_selection(short_udt_value, "field_testing", int32_type), evaluation_inputs{}),
exceptions::invalid_request_exception);
}
BOOST_AUTO_TEST_CASE(evaluate_column_mutation_attribute) {
auto s = make_simple_test_schema();
auto ttls = std::array{ int32_t(1), int32_t(2) };
auto timestamps = std::array{ int64_t(12345), int64_t(23456) };
auto ttl_of_s = column_mutation_attribute{
.kind = column_mutation_attribute::attribute_kind::ttl,
.column = column_value(&s->static_column_at(0)),
};
auto writetime_of_s = column_mutation_attribute{
.kind = column_mutation_attribute::attribute_kind::writetime,
.column = column_value(&s->static_column_at(0)),
};
auto ttl_of_r = column_mutation_attribute{
.kind = column_mutation_attribute::attribute_kind::ttl,
.column = column_value(&s->regular_column_at(0)),
};
auto writetime_of_r = column_mutation_attribute{
.kind = column_mutation_attribute::attribute_kind::writetime,
.column = column_value(&s->regular_column_at(0)),
};
auto null = cql3::raw_value::make_null();
auto [inputs1, inputs_data1] = make_evaluation_inputs(s, {
{"pk", mutation_column_value{make_int_raw(3)}},
{"ck", mutation_column_value{make_int_raw(4)}},
{"s", mutation_column_value{make_int_raw(3), 12345, 7}},
{"r", mutation_column_value{make_int_raw(3), 23456, 8}}});
BOOST_REQUIRE_EQUAL(evaluate(ttl_of_s, inputs1), make_int_raw(7));
BOOST_REQUIRE_EQUAL(evaluate(writetime_of_s, inputs1), make_bigint_raw(12345));
BOOST_REQUIRE_EQUAL(evaluate(ttl_of_r, inputs1), make_int_raw(8));
BOOST_REQUIRE_EQUAL(evaluate(writetime_of_r, inputs1), make_bigint_raw(23456));
auto [inputs2, inputs_data2] = make_evaluation_inputs(s, {
{"pk", make_int_raw(3)},
{"ck", make_int_raw(4)},
{"s", null},
{"r", null}});
BOOST_REQUIRE_EQUAL(evaluate(ttl_of_s, inputs2), null);
BOOST_REQUIRE_EQUAL(evaluate(writetime_of_s, inputs2), null);
BOOST_REQUIRE_EQUAL(evaluate(ttl_of_r, inputs2), null);
BOOST_REQUIRE_EQUAL(evaluate(writetime_of_r, inputs2), null);
}
// It should be possible to prepare an empty conjunction
BOOST_AUTO_TEST_CASE(prepare_conjunction_empty) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression conj_empty = conjunction{
.children = {},
};
expression expected = make_bool_const(true);
BOOST_REQUIRE_EQUAL(prepare_expression(conj_empty, db, "test_ks", table_schema.get(), nullptr), expected);
BOOST_REQUIRE_EQUAL(prepare_expression(conj_empty, db, "test_ks", table_schema.get(), make_receiver(boolean_type)),
expected);
}
// Preparing an empty conjunction should check that the type of receiver is correct
BOOST_AUTO_TEST_CASE(prepare_conjunction_empty_with_int_receiver) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression conj_empty = conjunction{
.children = {},
};
::lw_shared_ptr<column_specification> int_receiver = make_receiver(int32_type);
BOOST_REQUIRE_THROW(prepare_expression(conj_empty, db, "test_ks", table_schema.get(), int_receiver),
exceptions::invalid_request_exception);
}
// Prepare a conjunction whose only element is the constant `false`
BOOST_AUTO_TEST_CASE(prepare_conjunction_one_untyped_const_false) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression conj_one = conjunction{
.children = {make_bool_untyped("false")}};
expression expected = make_bool_const(false);
BOOST_REQUIRE_EQUAL(prepare_expression(conj_one, db, "test_ks", table_schema.get(), nullptr), expected);
BOOST_REQUIRE_EQUAL(prepare_expression(conj_one, db, "test_ks", table_schema.get(), make_receiver(boolean_type)),
expected);
}
// Prepare a conjunction whose only element is the constant `true`
BOOST_AUTO_TEST_CASE(prepare_conjunction_one_untyped_const_true) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression conj_one = conjunction{
.children = {make_bool_untyped("true")}};
expression expected = make_bool_const(true);
BOOST_REQUIRE_EQUAL(prepare_expression(conj_one, db, "test_ks", table_schema.get(), nullptr), expected);
BOOST_REQUIRE_EQUAL(prepare_expression(conj_one, db, "test_ks", table_schema.get(), make_receiver(boolean_type)),
expected);
}
// Prepare a conjunction whose only element is the constant `null`
BOOST_AUTO_TEST_CASE(prepare_conjunction_one_untyped_const_null) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression conj_one = conjunction{
.children = {make_null_untyped()}};
expression expected = constant::make_null(boolean_type);
BOOST_REQUIRE_EQUAL(prepare_expression(conj_one, db, "test_ks", table_schema.get(), nullptr), expected);
BOOST_REQUIRE_EQUAL(prepare_expression(conj_one, db, "test_ks", table_schema.get(), make_receiver(boolean_type)),
expected);
}
// Try preparing a conjunction which contains an integer `0`.
// It shouldn't be possible, conjunctions don't accept integers and 0 can't be cast to bool.
BOOST_AUTO_TEST_CASE(prepare_conjunction_one_int_untyped_const_0) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression conj_one = conjunction{
.children = {make_int_untyped("0")}};
BOOST_REQUIRE_THROW(prepare_expression(conj_one, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
BOOST_REQUIRE_THROW(prepare_expression(conj_one, db, "test_ks", table_schema.get(), make_receiver(boolean_type)),
exceptions::invalid_request_exception);
}
// Preparing 'true AND true AND 1 AND true` should throw an error
BOOST_AUTO_TEST_CASE(prepare_conjunction_bools_and_one_int_untyped_const_0) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression conj = conjunction{
.children = {make_bool_untyped("true"),
make_bool_untyped("true"),
make_int_untyped("1"),
make_bool_untyped("true")}};
BOOST_REQUIRE_THROW(prepare_expression(conj, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
BOOST_REQUIRE_THROW(prepare_expression(conj, db, "test_ks", table_schema.get(), make_receiver(boolean_type)),
exceptions::invalid_request_exception);
}
// AND can only be used to AND booleans, not the binary AND on integers
BOOST_AUTO_TEST_CASE(prepare_conjunction_and_of_ints_is_invalid) {
schema_ptr table_schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression conj_one = conjunction{
.children = {make_int_untyped("0"),
make_int_untyped("1")}};
BOOST_REQUIRE_THROW(prepare_expression(conj_one, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
BOOST_REQUIRE_THROW(prepare_expression(conj_one, db, "test_ks", table_schema.get(), make_receiver(int32_type)),
exceptions::invalid_request_exception);
}
// Prepare a conjunction with many elements:
// 'true AND true AND b1 AND (false AND b2)'
BOOST_AUTO_TEST_CASE(prepare_conjunction_many_elements) {
schema_ptr table_schema = schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("b1", boolean_type, column_kind::regular_column)
.with_column("b2", boolean_type, column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression sub_conj = conjunction{
.children = {make_bool_untyped("false"),
unresolved_identifier{::make_shared<column_identifier_raw>("b2", false)}}};
expression conj_many = conjunction{
.children = {make_bool_untyped("true"),
make_bool_untyped("false"),
unresolved_identifier{
unresolved_identifier{::make_shared<column_identifier_raw>("b1", false)},
},
sub_conj}};
expression expected =
conjunction{.children = {make_bool_const(true), make_bool_const(false),
column_value(table_schema->get_column_definition("b1")),
conjunction{.children = {make_bool_const(false),
column_value(table_schema->get_column_definition("b2"))}}}};
BOOST_REQUIRE_EQUAL(prepare_expression(conj_many, db, "test_ks", table_schema.get(), nullptr), expected);
BOOST_REQUIRE_EQUAL(prepare_expression(conj_many, db, "test_ks", table_schema.get(), make_receiver(boolean_type)),
expected);
// Integer receiver is rejected
BOOST_REQUIRE_THROW(prepare_expression(conj_many, db, "test_ks", table_schema.get(), make_receiver(int32_type)),
exceptions::invalid_request_exception);
}
// Test that preparing the given binary operator works and produces the expected result.
void test_prepare_good_binary_operator(expression good_binop_unprepared,
expression expected_prepared,
data_dictionary::database db,
const schema_ptr& table_schema) {
// Preparing without a receiver works as expected
BOOST_REQUIRE_EQUAL(prepare_expression(good_binop_unprepared, db, "test_ks", table_schema.get(), nullptr),
expected_prepared);
// Preparing with boolean receiver works as expected
BOOST_REQUIRE_EQUAL(
prepare_expression(good_binop_unprepared, db, "test_ks", table_schema.get(), make_receiver(boolean_type)),
expected_prepared);
// reversed boolean type is also accepted
BOOST_REQUIRE_EQUAL(prepare_expression(good_binop_unprepared, db, "test_ks", table_schema.get(),
make_receiver(reversed_type_impl::get_instance(boolean_type))),
expected_prepared);
// Receivers with non-bool type are rejected.
// Potentially we could allow receivers that are tuple<bool, ...>.
// Both Scylla and Cassandra allow to insert a single value in place of a tuple without adding the parenthesis.
// So something like:
// INSERT INTO tab (pk_int, float_bool_tuple) VALUES (123, 321.456)'
// Would insert a tuple value (321.456, null)
// There is no need to write VALUES (123, (321.456,))
// In my opinion this kind of hidden type conversion is harmful and bug inducing.
// We should allow it only in places where it's needed to stay compatible with Cassandra.
std::vector<data_type> invalid_receiver_types = {
byte_type,
short_type,
int32_type,
long_type,
ascii_type,
bytes_type,
utf8_type,
date_type,
timeuuid_type,
timestamp_type,
simple_date_type,
time_type,
uuid_type,
inet_addr_type,
float_type,
double_type,
varint_type,
decimal_type,
counter_type,
duration_type,
empty_type,
list_type_impl::get_instance(boolean_type, false),
list_type_impl::get_instance(boolean_type, true),
set_type_impl::get_instance(boolean_type, false),
set_type_impl::get_instance(boolean_type, true),
map_type_impl::get_instance(boolean_type, boolean_type, false),
map_type_impl::get_instance(boolean_type, boolean_type, true),
tuple_type_impl::get_instance({boolean_type}),
tuple_type_impl::get_instance({boolean_type, float_type}),
tuple_type_impl::get_instance({utf8_type, float_type}),
user_type_impl::get_instance("test_ks", "test_ut", {"field1", "field2"}, {boolean_type, float_type}, false),
user_type_impl::get_instance("test_ks", "test_ut", {"field1", "field2"}, {boolean_type, float_type}, true)};
for (const data_type& invalid_receiver_type : invalid_receiver_types) {
BOOST_REQUIRE_THROW(prepare_expression(good_binop_unprepared, db, "test_ks", table_schema.get(),
make_receiver(invalid_receiver_type)),
exceptions::invalid_request_exception);
BOOST_REQUIRE_THROW(prepare_expression(good_binop_unprepared, db, "test_ks", table_schema.get(),
make_receiver(reversed_type_impl::get_instance(invalid_receiver_type))),
exceptions::invalid_request_exception);
}
}
// Expected valid type for RHS values.
// We must know which values are valid to generate the invalid ones.
enum struct expected_rhs_type {
// float
float_type,
// text/ascii
string_type,
// tuple<float, int, text, double>
multi_column_tuple,
// list<float>
float_in_list,
// list<tuple<float, int, text, double>
multi_column_tuple_in_list,
// IS_NOT allows only NULL as the RHS, everything else is invalid
is_not_null_rhs
};
// Generates invalid RHS values for use in prepare_binary_operator tests.
// The argument specifies what type of RHS values are right, so we can avoid them when generating the wrong ones.
std::vector<expression> get_invalid_rhs_values(expected_rhs_type expected_rhs) {
// Start by adding values that are wrong in all prepare_binary_operator tests
std::vector<expression> invalid_rhs_vals = {
make_bool_untyped("true"), make_duration_untyped("365d"), make_hex_untyped("0xdeadbeef"),
// A tuple where the third element has a type that doesn't match the one expected by multi_column_tuple
tuple_constructor{.elements =
{
make_float_untyped("123.45"),
make_int_untyped("234"),
make_bool_untyped("true"),
make_float_untyped("45.67"),
}},
// A tuple with too many elements for multi_column_tuple.
// A tuple with too little elements doesn't cause an error - CQL accepts it, the missing fields are assumed to
// be null.
tuple_constructor{.elements =
{
make_float_untyped("123.45"),
make_int_untyped("234"),
make_string_untyped("hello"),
make_float_untyped("45.67"),
make_float_untyped("123.45"),
}},
collection_constructor{.style = collection_constructor::style_type::set, .elements = {}},
collection_constructor{.style = collection_constructor::style_type::set,
.elements = {make_float_untyped("12.3"), make_float_untyped("5.6")}},
collection_constructor{.style = collection_constructor::style_type::map, .elements = {}},
collection_constructor{
.style = collection_constructor::style_type::map,
.elements = {tuple_constructor{.elements = {make_float_untyped("1"), make_float_untyped("2")}},
tuple_constructor{.elements = {make_float_untyped("3"), make_float_untyped("4")}}}},
usertype_constructor{.elements = {}},
usertype_constructor{.elements = {{column_identifier("field1", false), make_float_untyped("1")},
{column_identifier("field2", false), make_float_untyped("2")}}}};
// `float_int_tuple = 1.23` is a valid expression, so we have to avoid adding int/float values for multi_column
// tests. This is allowed in both Cassandra and Scylla, and is equivalent to writing `float_int_tuple = (1.23,
// null)`
if (expected_rhs != expected_rhs_type::float_type && expected_rhs != expected_rhs_type::multi_column_tuple) {
invalid_rhs_vals.push_back(make_int_untyped("123"));
invalid_rhs_vals.push_back(make_float_untyped("56.78"));
}
if (expected_rhs != expected_rhs_type::string_type) {
invalid_rhs_vals.push_back(make_string_untyped("good_day"));
}
if (expected_rhs != expected_rhs_type::multi_column_tuple) {
invalid_rhs_vals.push_back(tuple_constructor{.elements = {}});
invalid_rhs_vals.push_back(tuple_constructor{.elements = {
make_float_untyped("123.45"),
make_int_untyped("234"),
make_string_untyped("hi"),
make_float_untyped("45.67"),
}});
invalid_rhs_vals.push_back(tuple_constructor{.elements = {
make_float_untyped("123.45"),
make_int_untyped("234"),
make_string_untyped("hi"),
}});
}
if (expected_rhs != expected_rhs_type::float_in_list &&
expected_rhs != expected_rhs_type::multi_column_tuple_in_list) {
invalid_rhs_vals.push_back(collection_constructor{
.style = collection_constructor::style_type::list,
.elements = {make_float_untyped("123.45"), make_float_untyped("732.2"), make_float_untyped("42.1")}});
invalid_rhs_vals.push_back(collection_constructor{
.style = collection_constructor::style_type::list,
.elements = {make_float_untyped("232"), make_float_untyped("121"), make_float_untyped("937")}});
}
if (expected_rhs != expected_rhs_type::multi_column_tuple_in_list) {
invalid_rhs_vals.push_back(collection_constructor{.style = collection_constructor::style_type::list,
.elements = {
tuple_constructor{.elements =
{
make_float_untyped("123.45"),
make_int_untyped("234"),
make_string_untyped("hi"),
make_float_untyped("45.67"),
}},
tuple_constructor{.elements =
{
make_float_untyped("231.1"),
make_int_untyped("232"),
make_string_untyped("dfdf"),
make_float_untyped("76.54"),
}},
}});
}
if (expected_rhs != expected_rhs_type::float_in_list &&
expected_rhs != expected_rhs_type::multi_column_tuple_in_list) {
invalid_rhs_vals.push_back(
collection_constructor{.style = collection_constructor::style_type::list, .elements = {}});
}
return invalid_rhs_vals;
}
// Test preparing the given binary_operator with various invalid RHS values.
// The values are generated using get_invalid_rhs_values().
void test_prepare_binary_operator_invalid_rhs_values(const expression& good_binop,
expected_rhs_type expected_rhs,
data_dictionary::database db,
const schema_ptr& table_schema) {
std::vector<expression> invalid_rhs_vals = get_invalid_rhs_values(expected_rhs);
for (const expression& invalid_rhs : invalid_rhs_vals) {
binary_operator invalid_binop = as<binary_operator>(good_binop);
invalid_binop.rhs = invalid_rhs;
BOOST_REQUIRE_THROW(prepare_expression(invalid_binop, db, "test_ks", table_schema.get(), nullptr),
exceptions::invalid_request_exception);
BOOST_REQUIRE_THROW(
prepare_expression(invalid_binop, db, "test_ks", table_schema.get(), make_receiver(boolean_type)),
exceptions::invalid_request_exception);
}
}
// The tests iterate over all possible comparison_orders so a convenience function is convenient.
std::array<comparison_order, 2> get_possible_comparison_orders() {
return {comparison_order::cql, comparison_order::clustering};
}
// Test preparing a binary_operator with operations: =, !=, <, <=, >, >=
// The test enumerates various possible LHS values and tries all the operators for each of them.
// The LHS values always are of type float to make testing easy.
// This means that multi-column LHS are not tested in here and need to be tested in a separate test.
// The same goes for reversed_type.
BOOST_AUTO_TEST_CASE(prepare_binary_operator_eq_neq_lt_lte_gt_gte) {
schema_ptr table_schema =
schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("float_col", float_type, column_kind::regular_column)
.with_column("reversed_float_col", reversed_type_impl::get_instance(float_type))
.with_column("float_list_col", list_type_impl::get_instance(float_type, true), column_kind::regular_column)
.with_column("frozen_float_list_col", list_type_impl::get_instance(float_type, false),
column_kind::regular_column)
.with_column("double_float_map_col", map_type_impl::get_instance(double_type, float_type, true),
column_kind::regular_column)
.with_column("frozen_double_float_map_col", map_type_impl::get_instance(double_type, float_type, false),
column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
// `float_col`
expression unprepared_float_col =
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("float_col", false)};
expression prepared_float_col = column_value(table_schema->get_column_definition("float_col"));
// `float_list_col[123]`
expression unprepared_subscripted_float_list =
subscript{.val = unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("float_list_col", false)},
.sub = make_int_untyped("123")};
expression prepared_subscripted_float_list =
subscript{.val = column_value(table_schema->get_column_definition("float_list_col")),
.sub = make_int_const(123),
.type = float_type};
// `frozen_float_list_col[123]`
expression unprepared_subscripted_frozen_float_list = subscript{
.val = unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("frozen_float_list_col", false)},
.sub = make_int_untyped("123")};
expression prepared_subscripted_frozen_float_list =
subscript{.val = column_value(table_schema->get_column_definition("frozen_float_list_col")),
.sub = make_int_const(123),
.type = float_type};
// `double_float_map_col[123.4]`
expression unprepared_subscripted_double_float_map = subscript{
.val = unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("double_float_map_col", false)},
.sub = make_float_untyped("123.4")};
expression prepared_subscripted_double_float_map =
subscript{.val = column_value(table_schema->get_column_definition("double_float_map_col")),
.sub = make_double_const(123.4),
.type = float_type};
// `frozen_double_float_map_col[123.4]`
expression unprepared_subscripted_frozen_double_float_map = subscript{
.val =
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("frozen_double_float_map_col", false)},
.sub = make_float_untyped("123.4")};
expression prepared_subscripted_frozen_double_float_map =
subscript{.val = column_value(table_schema->get_column_definition("frozen_double_float_map_col")),
.sub = make_double_const(123.4),
.type = float_type};
// `double_float_map_col[123]` <- int index should work where double is expected
expression unprepared_subscripted_double_float_map_with_int_index = subscript{
.val = unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("double_float_map_col", false)},
.sub = make_int_untyped("123")};
expression prepared_subscripted_double_float_map_with_int_index =
subscript{.val = column_value(table_schema->get_column_definition("double_float_map_col")),
.sub = make_double_const(123),
.type = float_type};
std::vector<std::pair<expression, expression>> possible_lhs_vals = {
{unprepared_float_col, prepared_float_col},
{unprepared_subscripted_float_list, prepared_subscripted_float_list},
{unprepared_subscripted_frozen_float_list, prepared_subscripted_frozen_float_list},
{unprepared_subscripted_double_float_map, prepared_subscripted_double_float_map},
{unprepared_subscripted_frozen_double_float_map, prepared_subscripted_frozen_double_float_map},
{unprepared_subscripted_double_float_map_with_int_index, prepared_subscripted_double_float_map_with_int_index}};
std::vector<oper_t> possible_operations = {oper_t::EQ, oper_t::NEQ, oper_t::LT,
oper_t::LTE, oper_t::GT, oper_t::GTE};
for (auto [unprepared_lhs, prepared_lhs] : possible_lhs_vals) {
for (const oper_t& op : possible_operations) {
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare = binary_operator(unprepared_lhs, op, make_float_untyped("123.4"), comp_order);
expression expected = binary_operator(prepared_lhs, op, make_float_const(123.4), comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::float_type, db,
table_schema);
}
}
}
}
// Test operations =, !=, <, <=, >, >= with a LHS column that has reversed type.
// The prepared RHS should also have inherit the reversed type.
BOOST_AUTO_TEST_CASE(prepare_binary_operator_eq_neq_lt_lte_gt_gte_reversed_type) {
schema_ptr table_schema = schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("reversed_float_col", reversed_type_impl::get_instance(float_type),
column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
std::vector<oper_t> possible_operations = {oper_t::EQ, oper_t::NEQ, oper_t::LT,
oper_t::LTE, oper_t::GT, oper_t::GTE};
for (const oper_t& op : possible_operations) {
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare = binary_operator(
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("reversed_float_col", false)}, op,
make_float_untyped("123.4"), comp_order);
constant rhs_const = make_float_const(123.4);
rhs_const.type = reversed_type_impl::get_instance(float_type);
expression expected = binary_operator(
column_value(table_schema->get_column_definition("reversed_float_col")), op, rhs_const, comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::float_type, db,
table_schema);
}
}
}
// Test operations =, !=, <, <=, >, >= with a multi-column LHS.
BOOST_AUTO_TEST_CASE(prepare_binary_operator_eq_neq_lt_lte_gt_gte_multi_column) {
schema_ptr table_schema =
schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("c1", float_type, column_kind::clustering_key)
.with_column("c2", int32_type, column_kind::clustering_key)
.with_column("c3", utf8_type, column_kind::clustering_key)
.with_column("c4", reversed_type_impl::get_instance(double_type), column_kind::clustering_key)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression unprepared_lhs = tuple_constructor{
.elements = {unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("c1", false)},
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("c2", false)},
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("c3", false)},
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("c4", false)}},
.type = nullptr};
expression prepared_lhs =
tuple_constructor{.elements = {column_value(table_schema->get_column_definition("c1")),
column_value(table_schema->get_column_definition("c2")),
column_value(table_schema->get_column_definition("c3")),
column_value(table_schema->get_column_definition("c4"))},
.type = tuple_type_impl::get_instance(
{float_type, int32_type, utf8_type, reversed_type_impl::get_instance(double_type)})};
expression unprepared_rhs =
tuple_constructor{.elements = {make_float_untyped("123.4"), make_int_untyped("1234"),
make_string_untyped("hello"), make_float_untyped("112233.44")},
.type = nullptr};
expression prepared_rhs = make_tuple_const(
{make_float_raw(123.4), make_int_raw(1234), make_text_raw("hello"), make_double_raw(112233.44)},
{float_type, int32_type, utf8_type, double_type});
std::vector<oper_t> possible_operations = {oper_t::EQ, oper_t::NEQ, oper_t::LT,
oper_t::LTE, oper_t::GT, oper_t::GTE};
for (const oper_t& op : possible_operations) {
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare = binary_operator(unprepared_lhs, op, unprepared_rhs, comp_order);
expression expected = binary_operator(prepared_lhs, op, prepared_rhs, comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::multi_column_tuple, db,
table_schema);
}
}
}
// `float_col IN ()`
BOOST_AUTO_TEST_CASE(prepare_binary_operator_float_col_in_empty_list) {
schema_ptr table_schema = schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("float_col", float_type, column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare = binary_operator(
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("float_col", false)}, oper_t::IN,
collection_constructor{.style = collection_constructor::style_type::list, .elements = {}}, comp_order);
expression expected =
binary_operator(column_value(table_schema->get_column_definition("float_col")), oper_t::IN,
constant(make_list_raw({}), list_type_impl::get_instance(float_type, false)), comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::float_in_list, db, table_schema);
}
}
// `float_col IN (1, 2.3)`
BOOST_AUTO_TEST_CASE(prepare_binary_operator_float_col_in_1_2_dot_3) {
schema_ptr table_schema = schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("float_col", float_type, column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare = binary_operator(
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("float_col", false)}, oper_t::IN,
collection_constructor{.style = collection_constructor::style_type::list,
.elements = {make_int_untyped("1"), make_float_untyped("2.3")}},
comp_order);
expression expected =
binary_operator(column_value(table_schema->get_column_definition("float_col")), oper_t::IN,
constant(make_list_raw({make_float_raw(1), make_float_raw(2.3)}),
list_type_impl::get_instance(float_type, false)),
comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::float_in_list, db, table_schema);
}
}
// `float_col IN (1, 2, 3, 4)`
BOOST_AUTO_TEST_CASE(prepare_binary_operator_float_col_in_1_2_3_4) {
schema_ptr table_schema = schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("float_col", float_type, column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare = binary_operator(
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("float_col", false)}, oper_t::IN,
collection_constructor{.style = collection_constructor::style_type::list,
.elements =
{
make_int_untyped("1"),
make_int_untyped("2"),
make_int_untyped("3"),
make_int_untyped("4"),
}},
comp_order);
expression expected = binary_operator(
column_value(table_schema->get_column_definition("float_col")), oper_t::IN,
constant(make_list_raw({make_float_raw(1), make_float_raw(2), make_float_raw(3), make_float_raw(4)}),
list_type_impl::get_instance(float_type, false)),
comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::float_in_list, db, table_schema);
}
}
// reverse_float_col IN ()
BOOST_AUTO_TEST_CASE(prepare_binary_operato_reverse_float_col_in_empty_list) {
schema_ptr table_schema =
schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("reverse_float_col", reversed_type_impl::get_instance(float_type), column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare = binary_operator(
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("reverse_float_col", false)},
oper_t::IN, collection_constructor{.style = collection_constructor::style_type::list, .elements = {}},
comp_order);
expression expected =
binary_operator(column_value(table_schema->get_column_definition("reverse_float_col")), oper_t::IN,
constant(make_list_raw({}), list_type_impl::get_instance(float_type, false)), comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::float_in_list, db, table_schema);
}
}
// `reverse_float_col IN (1.2, 2.3)`
BOOST_AUTO_TEST_CASE(prepare_binary_operator_float_col_in_1_dot_2_2_dot_3) {
schema_ptr table_schema =
schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("reverse_float_col", reversed_type_impl::get_instance(float_type), column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare = binary_operator(
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("reverse_float_col", false)},
oper_t::IN,
collection_constructor{.style = collection_constructor::style_type::list,
.elements = {make_float_untyped("1.2"), make_float_untyped("2.3")}},
comp_order);
expression expected =
binary_operator(column_value(table_schema->get_column_definition("reverse_float_col")), oper_t::IN,
constant(make_list_raw({make_float_raw(1.2), make_float_raw(2.3)}),
list_type_impl::get_instance(float_type, false)),
comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::float_in_list, db, table_schema);
}
}
// `(float_col, int_col, text_col, reverse_double_col) IN ()`
BOOST_AUTO_TEST_CASE(prepare_binary_operator_multi_col_in_empty_list) {
schema_ptr table_schema = schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("float_col", float_type, column_kind::clustering_key)
.with_column("int_col", int32_type, column_kind::clustering_key)
.with_column("text_col", utf8_type, column_kind::clustering_key)
.with_column("reverse_double_col", reversed_type_impl::get_instance(double_type))
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression unprepared_lhs = tuple_constructor{
.elements =
{
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("float_col", false)},
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("int_col", false)},
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("text_col", false)},
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("reverse_double_col", false)},
},
.type = nullptr};
data_type tuple_type = tuple_type_impl::get_instance(
{float_type, int32_type, utf8_type, reversed_type_impl::get_instance(double_type)});
expression prepared_lhs =
tuple_constructor{.elements = {column_value(table_schema->get_column_definition("float_col")),
column_value(table_schema->get_column_definition("int_col")),
column_value(table_schema->get_column_definition("text_col")),
column_value(table_schema->get_column_definition("reverse_double_col"))},
.type = tuple_type};
expression unprepared_rhs =
collection_constructor{.style = collection_constructor::style_type::list, .elements = {}};
// reversed is removed!
expression prepared_rhs = constant(
make_list_raw({}), list_type_impl::get_instance(
tuple_type_impl::get_instance({float_type, int32_type, utf8_type, double_type}), false));
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare = binary_operator(unprepared_lhs, oper_t::IN, unprepared_rhs, comp_order);
expression expected = binary_operator(prepared_lhs, oper_t::IN, prepared_rhs, comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::multi_column_tuple_in_list, db,
table_schema);
}
}
// `(float_col, int_col, text_col, reverse_double_col) IN ((1.2, 3, 'four', 8.9), (5, 6, 'seven', 10.11))`
BOOST_AUTO_TEST_CASE(prepare_binary_operator_multi_col_in_values) {
schema_ptr table_schema = schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("float_col", float_type, column_kind::clustering_key)
.with_column("int_col", int32_type, column_kind::clustering_key)
.with_column("text_col", utf8_type, column_kind::clustering_key)
.with_column("reverse_double_col", reversed_type_impl::get_instance(double_type))
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression unprepared_lhs = tuple_constructor{
.elements =
{
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("float_col", false)},
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("int_col", false)},
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("text_col", false)},
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("reverse_double_col", false)},
},
.type = nullptr};
data_type tuple_type = tuple_type_impl::get_instance(
{float_type, int32_type, utf8_type, reversed_type_impl::get_instance(double_type)});
expression prepared_lhs =
tuple_constructor{.elements = {column_value(table_schema->get_column_definition("float_col")),
column_value(table_schema->get_column_definition("int_col")),
column_value(table_schema->get_column_definition("text_col")),
column_value(table_schema->get_column_definition("reverse_double_col"))},
.type = tuple_type};
expression unprepared_rhs =
collection_constructor{.style = collection_constructor::style_type::list,
.elements = {tuple_constructor{.elements =
{
make_float_untyped("1.2"),
make_int_untyped("3"),
make_string_untyped("four"),
make_float_untyped("8.9"),
}},
tuple_constructor{.elements = {
make_int_untyped("5"),
make_int_untyped("6"),
make_string_untyped("seven"),
make_float_untyped("10.11"),
}}}};
raw_value prepared_rhs_raw = make_list_raw(
{make_tuple_raw({make_float_raw(1.2), make_int_raw(3), make_text_raw("four"), make_double_raw(8.9)}),
make_tuple_raw({make_float_raw(5), make_int_raw(6), make_text_raw("seven"), make_double_raw(10.11)})});
// reversed is removed!
data_type prepared_rhs_type = list_type_impl::get_instance(
tuple_type_impl::get_instance({float_type, int32_type, utf8_type, double_type}), false);
expression prepared_rhs = constant(prepared_rhs_raw, prepared_rhs_type);
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare = binary_operator(unprepared_lhs, oper_t::IN, unprepared_rhs, comp_order);
expression expected = binary_operator(prepared_lhs, oper_t::IN, prepared_rhs, comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::multi_column_tuple_in_list, db,
table_schema);
}
}
BOOST_AUTO_TEST_CASE(prepare_binary_operator_contains) {
schema_ptr table_schema =
schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("float_list", list_type_impl::get_instance(float_type, true), column_kind::regular_column)
.with_column("frozen_float_list", list_type_impl::get_instance(float_type, false),
column_kind::regular_column)
.with_column("float_set", set_type_impl::get_instance(float_type, true), column_kind::regular_column)
.with_column("frozen_float_set", set_type_impl::get_instance(float_type, false),
column_kind::regular_column)
.with_column("double_float_map", map_type_impl::get_instance(double_type, float_type, true),
column_kind::regular_column)
.with_column("frozen_double_float_map", map_type_impl::get_instance(double_type, float_type, false),
column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
std::vector<const char*> possible_lhs_col_names = {"float_list", "frozen_float_list",
"float_set", "frozen_float_set",
"double_float_map", "frozen_double_float_map"};
std::vector<std::pair<untyped_constant, constant>> possible_rhs_vals = {
{make_int_untyped("123"), make_float_const(123)},
{
make_float_untyped("123.45"),
make_float_const(123.45),
}};
for (const char* lhs_col_name : possible_lhs_col_names) {
for (auto& [unprepared_rhs, prepared_rhs] : possible_rhs_vals) {
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare = binary_operator(
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>(lhs_col_name, false)},
oper_t::CONTAINS, unprepared_rhs, comp_order);
expression expected = binary_operator(column_value(table_schema->get_column_definition(lhs_col_name)),
oper_t::CONTAINS, prepared_rhs, comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::float_type, db,
table_schema);
}
}
}
}
BOOST_AUTO_TEST_CASE(prepare_binary_operator_contains_key) {
schema_ptr table_schema =
schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("double_float_map", map_type_impl::get_instance(double_type, float_type, true),
column_kind::regular_column)
.with_column("frozen_double_float_map", map_type_impl::get_instance(double_type, float_type, false),
column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
std::vector<const char*> possible_lhs_col_names = {"double_float_map", "frozen_double_float_map"};
std::vector<std::pair<untyped_constant, constant>> possible_rhs_vals = {
{make_int_untyped("123"), make_double_const(123)},
{
make_float_untyped("123.45"),
make_double_const(123.45),
}};
for (const char* lhs_col_name : possible_lhs_col_names) {
for (auto& [unprepared_rhs, prepared_rhs] : possible_rhs_vals) {
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare = binary_operator(
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>(lhs_col_name, false)},
oper_t::CONTAINS_KEY, unprepared_rhs, comp_order);
expression expected = binary_operator(column_value(table_schema->get_column_definition(lhs_col_name)),
oper_t::CONTAINS_KEY, prepared_rhs, comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::float_type, db,
table_schema);
}
}
}
}
BOOST_AUTO_TEST_CASE(prepare_binary_operator_like) {
schema_ptr table_schema = schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("text_col", utf8_type, column_kind::regular_column)
.with_column("ascii_col", ascii_type, column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
std::vector<const char*> possible_lhs_col_names = {"text_col", "ascii_col"};
for (const char* lhs_col_name : possible_lhs_col_names) {
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare = binary_operator(
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>(lhs_col_name, false)}, oper_t::LIKE,
make_string_untyped("some%hing"), comp_order);
const column_definition* lhs_col_def = table_schema->get_column_definition(lhs_col_name);
expression expected = binary_operator(column_value(lhs_col_def), oper_t::LIKE,
constant(make_text_raw("some%hing"), lhs_col_def->type), comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::string_type, db,
table_schema);
}
}
}
BOOST_AUTO_TEST_CASE(prepare_binary_operator_is_not_null) {
schema_ptr table_schema = schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("float_col", float_type, column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare =
binary_operator(unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("float_col", false)},
oper_t::IS_NOT, make_null_untyped(), comp_order);
expression expected = binary_operator(column_value(table_schema->get_column_definition("float_col")),
oper_t::IS_NOT, constant::make_null(float_type), comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::is_not_null_rhs, db,
table_schema);
}
}
// `float_col = NULL`, `float_col < NULL`, ...
// The RHS should be prepared as a NULL constant with float type.
BOOST_AUTO_TEST_CASE(prepare_binary_operator_with_null_rhs) {
schema_ptr table_schema = schema_builder("test_ks", "test_cf")
.with_column("pk", int32_type, column_kind::partition_key)
.with_column("float_col", float_type, column_kind::regular_column)
.build();
auto [db, db_data] = make_data_dictionary_database(table_schema);
std::vector<oper_t> possible_operations = {oper_t::EQ, oper_t::NEQ, oper_t::LT,
oper_t::LTE, oper_t::GT, oper_t::GTE};
for (const oper_t& op : possible_operations) {
for (const comparison_order& comp_order : get_possible_comparison_orders()) {
expression to_prepare = binary_operator(
unresolved_identifier{.ident = ::make_shared<column_identifier_raw>("float_col", false)}, op,
make_null_untyped(), comp_order);
expression expected = binary_operator(column_value(table_schema->get_column_definition("float_col")), op,
constant::make_null(float_type), comp_order);
test_prepare_good_binary_operator(to_prepare, expected, db, table_schema);
test_prepare_binary_operator_invalid_rhs_values(to_prepare, expected_rhs_type::float_type, db,
table_schema);
}
}
}
BOOST_AUTO_TEST_CASE(optimized_constant_like) {
auto check = [] (expression e, std::optional<sstring> target, bool expect_optimization, std::optional<sstring> pattern_arg = {}) {
auto optimized = optimize_like(e);
bool was_optimized = find_binop(optimized, [] (const binary_operator&) { return true; }) == nullptr;
if (was_optimized != expect_optimization) {
return false;
}
auto params = std::vector({target ? make_text_raw(*target) : raw_value::make_null()});
if (pattern_arg) {
params.push_back(make_text_raw(*pattern_arg));
}
return evaluate_with_bind_variables(optimized, params) == evaluate_with_bind_variables(e, params);
};
auto target_var = make_bind_variable(0, utf8_type);
auto pattern_var = make_bind_variable(1, utf8_type);
BOOST_REQUIRE(check(binary_operator(target_var, oper_t::LIKE, make_text_const("xx%")), "xxyyz", true));
BOOST_REQUIRE(check(binary_operator(target_var, oper_t::LIKE, make_text_const("xx%")), "qxyyz", true));
BOOST_REQUIRE(check(binary_operator(target_var, oper_t::LIKE, make_text_const("xx%")), std::nullopt, true));
BOOST_REQUIRE(check(binary_operator(target_var, oper_t::LIKE, pattern_var), "xxyyz", false, "xx%"));
BOOST_REQUIRE(check(binary_operator(target_var, oper_t::LIKE, pattern_var), "qxyyz", false, "xx%"));
BOOST_REQUIRE(check(binary_operator(target_var, oper_t::LIKE, pattern_var), std::nullopt, false, "xx%"));
// Verify that optimization works for subexpressions, not just top-level expressions
auto complex = make_conjunction(
binary_operator(target_var, oper_t::LIKE, make_text_const("xx%")),
// repeated for simplicity
binary_operator(target_var, oper_t::LIKE, make_text_const("xx%")));
BOOST_REQUIRE(check(std::move(complex), "xxyyz", true));
}
BOOST_AUTO_TEST_CASE(prepare_token_func_without_receiver) {
schema_ptr table_schema = make_three_pk_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
expression token_fun_call = function_call{
.func = functions::function_name::native_function("token"),
.args = {column_value(table_schema->get_column_definition("pk1")), make_column("pk2"), make_int_const(1234)}};
expression prepared_no_receiver = prepare_expression(token_fun_call, db, "test_ks", table_schema.get(), nullptr);
expression prepared_with_receiver =
prepare_expression(token_fun_call, db, "test_ks", table_schema.get(), make_receiver(long_type));
auto check_prepared = [&](const expression& prepared) {
const function_call* prepared_token = as_if<function_call>(&prepared);
BOOST_REQUIRE(prepared_token != nullptr);
const seastar::shared_ptr<functions::function>* token_fun =
std::get_if<shared_ptr<functions::function>>(&prepared_token->func);
BOOST_REQUIRE(token_fun != nullptr);
BOOST_REQUIRE((*token_fun)->name() == functions::function_name::native_function("token"));
std::vector<expression> expected_args = {column_value(table_schema->get_column_definition("pk1")),
column_value(table_schema->get_column_definition("pk2")),
make_int_const(1234)};
BOOST_REQUIRE_EQUAL(prepared_token->args, expected_args);
};
check_prepared(prepared_no_receiver);
check_prepared(prepared_with_receiver);
}
BOOST_AUTO_TEST_CASE(is_token_function_valid_call) {
function_call token_fun_call{.func = functions::function_name::native_function("token"), .args = {}};
BOOST_REQUIRE_EQUAL(is_token_function(token_fun_call), true);
}
BOOST_AUTO_TEST_CASE(is_token_function_invalid_call) {
function_call token_fun_call{.func = functions::function_name::native_function("invalid_function"), .args = {}};
BOOST_REQUIRE_EQUAL(is_token_function(token_fun_call), false);
}
BOOST_AUTO_TEST_CASE(is_token_function_valid_call_with_keyspace) {
function_call token_fun_call{.func = functions::function_name("system", "token"), .args = {}};
BOOST_REQUIRE_EQUAL(is_token_function(token_fun_call), true);
}
BOOST_AUTO_TEST_CASE(is_token_function_invalid_call_with_keyspace) {
function_call token_fun_call{.func = functions::function_name("system", "invalid_token"),
.args = {}};
BOOST_REQUIRE_EQUAL(is_token_function(token_fun_call), false);
}
BOOST_AUTO_TEST_CASE(is_token_function_invalid_call_with_invalid_keyspace) {
function_call token_fun_call{
.func = functions::function_name("invalid_keyspace", "token"), .args = {}};
BOOST_REQUIRE_EQUAL(is_token_function(token_fun_call), false);
}
BOOST_AUTO_TEST_CASE(is_token_function_prepared_token) {
schema_ptr table_schema = make_three_pk_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
function_call token_fun{
.func = functions::function_name::native_function("token"),
.args = {make_int_untyped("123"), make_int_untyped("443"), make_bind_variable(0, int32_type)}};
expression prepared = prepare_expression(token_fun, db, "test_ks", table_schema.get(), nullptr);
BOOST_REQUIRE_EQUAL(is_token_function(prepared), true);
}
BOOST_AUTO_TEST_CASE(is_token_function_prepared_nontoken) {
schema_ptr table_schema = make_three_pk_schema();
auto [db, db_data] = make_data_dictionary_database(table_schema);
function_call now_fun{.func = functions::function_name::native_function("now"), .args = {}};
expression prepared = prepare_expression(now_fun, db, "test_ks", table_schema.get(), nullptr);
BOOST_REQUIRE_EQUAL(is_token_function(prepared), false);
}
// Test the function expr::is_partition_token_for_schema
BOOST_AUTO_TEST_CASE(is_partition_token_for_schema_test) {
schema_ptr schema1 = make_simple_test_schema(); // partition key: (pk)
schema_ptr schema2 = make_three_pk_schema(); // partition key: (pk1, pk2, pk3)
schema_ptr schema3 = make_three_pk_schema(); // partition key: (pk1, pk2, pk3)
auto [db1, db1_data] = make_data_dictionary_database(schema1);
auto [db2, db2_data] = make_data_dictionary_database(schema2);
auto [db3, db3_data] = make_data_dictionary_database(schema3);
// Prepare token(pk)
expression unprepared_token_one_pk =
function_call{.func = functions::function_name::native_function("token"), .args = {make_column("pk")}};
expression prepared_token_one_pk =
prepare_expression(unprepared_token_one_pk, db1, "test_ks", schema1.get(), nullptr);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_one_pk, *schema1), true);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_one_pk, *schema2), false);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_one_pk, *schema3), false);
// Prepare token(pk1, pk2, pk3)
expression unprepared_token_three_pk =
function_call{.func = functions::function_name::native_function("token"),
.args = {make_column("pk1"), make_column("pk2"), make_column("pk3")}};
expression prepared_token_three_pk =
prepare_expression(unprepared_token_three_pk, db2, "test_ks", schema2.get(), nullptr);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_one_pk, *schema1), true);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_one_pk, *schema2), false);
// Same columns, but different schema!
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_one_pk, *schema3), false);
// Try preparing token(pk1, pk2), fail
expression unprepared_token_two_pk = function_call{.func = functions::function_name::native_function("token"),
.args = {make_column("pk1"), make_column("pk2")}};
BOOST_REQUIRE_THROW(prepare_expression(unprepared_token_two_pk, db2, "test_ks", schema2.get(), nullptr),
exceptions::invalid_request_exception);
// Prepare token(pk1, pk3, pk2) - wrong order of pk columns
expression unprepared_token_pk1_pk3_pk2 =
function_call{.func = functions::function_name::native_function("token"),
.args = {make_column("pk1"), make_column("pk3"), make_column("pk2")}};
expression prepared_token_pk1_pk3_pk2 =
prepare_expression(unprepared_token_pk1_pk3_pk2, db2, "test_ks", schema2.get(), nullptr);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_pk1_pk3_pk2, *schema1), false);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_pk1_pk3_pk2, *schema2), false);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_pk1_pk3_pk2, *schema3), false);
// Prepare token(pk1, pk1, pk3) - duplicate column
expression unprepared_token_pk1_pk1_pk2 =
function_call{.func = functions::function_name::native_function("token"),
.args = {make_column("pk1"), make_column("pk1"), make_column("pk3")}};
expression prepared_token_pk1_pk1_pk2 =
prepare_expression(unprepared_token_pk1_pk1_pk2, db2, "test_ks", schema2.get(), nullptr);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_pk1_pk1_pk2, *schema1), false);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_pk1_pk1_pk2, *schema2), false);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_pk1_pk1_pk2, *schema3), false);
// Prepare token(ck)
expression unprepared_token_ck =
function_call{.func = functions::function_name::native_function("token"), .args = {make_column("ck")}};
expression prepared_token_ck = prepare_expression(unprepared_token_ck, db1, "test_ks", schema1.get(), nullptr);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_ck, *schema1), false);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_ck, *schema2), false);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_ck, *schema3), false);
// Prepare token(1, 2, ?) - not a partition token
// The bind variable is there to prevent the prepared expression from turning into an expr::constant
expression unprepared_token_other =
function_call{.func = functions::function_name::native_function("token"),
.args = {make_int_untyped("1"), make_int_untyped("2"), make_bind_variable(0, int32_type)}};
expression prepared_token_other =
prepare_expression(unprepared_token_other, db2, "test_ks", schema2.get(), nullptr);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_other, *schema1), false);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_other, *schema2), false);
BOOST_REQUIRE_EQUAL(is_partition_token_for_schema(prepared_token_other, *schema3), false);
}
BOOST_AUTO_TEST_CASE(test_aggregation_depth) {
BOOST_REQUIRE_EQUAL(aggregation_depth(make_bigint_const(7)), 0);
auto schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(schema);
auto avg_sum_r = expression(
function_call{
.func = functions::function_name("", "avg"),
.args = {
function_call{
.func = functions::function_name("", "sum"),
.args = {
unresolved_identifier{
.ident = ::make_shared<column_identifier_raw>("r", false),
},
},
},
},
}
);
avg_sum_r = prepare_expression(avg_sum_r, db, "test_ks", schema.get(), nullptr);
BOOST_REQUIRE_EQUAL(aggregation_depth(avg_sum_r), 2);
// Now test something with an imbalance
auto avg_r = function_call{
.func = functions::function_name("", "avg"),
.args = {
unresolved_identifier{
.ident = ::make_shared<column_identifier_raw>("r", false),
},
},
};
auto compared = expression(binary_operator(avg_r, oper_t::EQ, avg_sum_r));
compared = prepare_expression(compared, db, "test_ks", schema.get(), nullptr);
BOOST_REQUIRE_EQUAL(aggregation_depth(compared), 2);
}
static
shared_ptr<functions::function>
make_two_arg_aggregate_function() {
return make_shared<db::functions::aggregate_function>(db::functions::stateless_aggregate_function{
.name = functions::function_name("foo", "my_agg"),
.result_type = int32_type,
.argument_types = { int32_type, int32_type },
// THe other fields are important, but not for test_levellize_aggregation_depth
});
}
BOOST_AUTO_TEST_CASE(test_levellize_aggregation_depth) {
auto schema = make_simple_test_schema();
auto [db, db_data] = make_data_dictionary_database(schema);
// my_agg(sum(r), r))
auto e = expression(
function_call{
.func = make_two_arg_aggregate_function(),
.args = {
function_call{
.func = functions::function_name::native_function("sum"),
.args = {
column_value(&schema->regular_column_at(0)),
},
},
column_value(&schema->regular_column_at(0)),
},
}
);
e = prepare_expression(e, db, "test_ks", schema.get(), nullptr);
BOOST_REQUIRE_EQUAL(aggregation_depth(e), 2);
e = levellize_aggregation_depth(e, 3); // Note: aggregation_depth(e) == 2 before the call
BOOST_REQUIRE_EQUAL(aggregation_depth(e), 3);
// Somewhat fragile, but easiest way to test entire structure
BOOST_REQUIRE_EQUAL(fmt::format("{:debug}", e), "foo.my_agg(system.sum(system.$$first$$(r)), system.$$first$$(system.$$first$$(r)))");
// Repeat the test, but for writetime(r) rather than r, to make sure we
// get first(writetime(r)) rather than writetime(first(r)) (#14715).
// my_agg(sum(r), writetime(r)))
auto e2 = expression(
function_call{
.func = make_two_arg_aggregate_function(),
.args = {
function_call{
.func = functions::function_name::native_function("sum"),
.args = {
column_value(&schema->regular_column_at(0)),
},
},
column_mutation_attribute{
.kind = column_mutation_attribute::attribute_kind::ttl, // conveniently returns int32_type like r
.column = column_value(&schema->regular_column_at(0)),
},
},
}
);
e2 = prepare_expression(e2, db, "test_ks", schema.get(), nullptr);
BOOST_REQUIRE_EQUAL(aggregation_depth(e2), 2);
e2 = levellize_aggregation_depth(e2, 3); // Note: aggregation_depth(e) == 2 before the call
BOOST_REQUIRE_EQUAL(aggregation_depth(e2), 3);
// Somewhat fragile, but easiest way to test entire structure
BOOST_REQUIRE_EQUAL(fmt::format("{:debug}", e2), "foo.my_agg(system.sum(system.$$first$$(r)), system.$$first$$(system.$$first$$(TTL(r))))");
}