// Copyright (C) 2023-present ScyllaDB // SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0 #include "cql3/column_identifier.hh" #include "cql3/util.hh" #include #include "types/types.hh" #include "types/list.hh" #include "types/map.hh" #include "types/vector.hh" #include #define BOOST_TEST_MODULE core #include #include #include #include "cql3/expr/expression.hh" #include "utils/overloaded_functor.hh" #include "utils/to_string.hh" #include #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("ks", "tab", make_shared("?", 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(col_name, true)}; } static function_call make_token(std::vector 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("p1", true)}, unresolved_identifier{::make_shared("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_or_vector, .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_or_vector, .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_vector_test) { collection_constructor int_vector { .style = collection_constructor::style_type::vector, .elements = {make_int_const(13), make_int_const(45), make_int_const(90)}, .type = vector_type_impl::get_instance(int32_type, 3) }; BOOST_REQUIRE_EQUAL(expr_print(int_vector), "[13, 45, 90]"); cql3::raw_value int_vector_const = evaluate(int_vector, query_options::DEFAULT); BOOST_REQUIRE_EQUAL(value_print(int_vector_const, int_vector), "[13, 45, 90]"); } 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_or_vector, .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({make_bool_const(true)})); BOOST_REQUIRE_EQUAL(boolean_factors(constant::make_null(boolean_type)), std::vector({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({bv1, bv2, bv3, bv4})} ), std::vector( {bv1, bv2, bv3, bv4} ) ); BOOST_REQUIRE_EQUAL( boolean_factors( conjunction{ std::vector({ make_conjunction(bv1, bv2), make_conjunction(bv3, bv4) }) } ), std::vector( {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(), 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, 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_vector_collection_constructor) { expression int_vector = make_vector_constructor({make_int_const(1), make_int_const(2), make_int_const(3)}, int32_type, 3); BOOST_REQUIRE_EQUAL(evaluate(int_vector, evaluation_inputs{}), make_int_vector_raw({1, 2, 3})); } BOOST_AUTO_TEST_CASE(evaluate_vector_collection_constructor_does_not_sort) { expression int_vector = make_vector_constructor({make_int_const(3), make_int_const(1), make_int_const(3), make_int_const(1)}, int32_type, 3); BOOST_REQUIRE_EQUAL(evaluate(int_vector, evaluation_inputs{}), make_int_vector_raw({3, 1, 3, 1})); } BOOST_AUTO_TEST_CASE(evaluate_vector_collection_constructor_with_null) { expression vector_with_null = make_vector_constructor({make_int_const(1), constant::make_null(int32_type), make_int_const(3)}, int32_type, 3); BOOST_REQUIRE_THROW(evaluate(vector_with_null, evaluation_inputs{}), exceptions::invalid_request_exception); } 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{}, 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::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::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::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::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>(), 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::min())), raw_value::make_null()); BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(std::numeric_limits::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::min())), raw_value::make_null()); BOOST_REQUIRE_EQUAL(evaluate_subscripted(map, make_int_const(std::numeric_limits::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("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("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("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("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("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("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("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("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("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("p1", true)}, unresolved_identifier{::make_shared("p2", true)}, unresolved_identifier{::make_shared("p3", true)}}); expression prepared = prepare_expression(tok, db, "test_ks", table_schema.get(), nullptr); std::vector 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(&prepared); BOOST_REQUIRE(token_fun_call != nullptr); BOOST_REQUIRE(is_token_function(*token_fun_call)); BOOST_REQUIRE(std::holds_alternative>(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()); 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 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 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 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(prepared) && is(as(prepared).arg)); ::lw_shared_ptr bind_var_receiver = as(as(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(prepared) && is(as(prepared).arg)); ::lw_shared_ptr bind_var_receiver = as(as(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(prepared) && is(as(prepared).arg)); ::lw_shared_ptr bind_var_receiver = as(as(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 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 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("c1", true)}, unresolved_identifier{.ident = ::make_shared("c2", true)}, unresolved_identifier{.ident = ::make_shared("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_or_vector_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_or_vector, .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_list = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(list_type)); expression expected_list = make_list_const({make_bigint_const(123), make_bigint_const(456), make_bigint_const(789)}, long_type); BOOST_REQUIRE_EQUAL(prepared_list, expected_list); data_type vector_type = vector_type_impl::get_instance(long_type, 3); expression prepared_vector = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(vector_type)); expression expected_vector = make_vector_const({make_bigint_const(123), make_bigint_const(456), make_bigint_const(789)}, long_type); BOOST_REQUIRE_EQUAL(prepared_vector, expected_vector); } // 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_or_vector, .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); // Vector type is not tested here because it is always frozen. } BOOST_AUTO_TEST_CASE(prepare_list_or_vector_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_or_vector, .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); data_type vector_type = vector_type_impl::get_instance(long_type, 0); // Should throw because we can't prepare an empty vector. BOOST_REQUIRE_THROW(prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(vector_type)), exceptions::invalid_request_exception); } BOOST_AUTO_TEST_CASE(prepare_list_or_vector_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_or_vector, .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_or_vector, .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(&prepared); BOOST_REQUIRE(prepared_constructor != nullptr); BOOST_REQUIRE_EQUAL(prepared_constructor->elements.size(), 3); bind_variable* prepared_bind_var = as_if(&prepared_constructor->elements[1]); BOOST_REQUIRE(prepared_bind_var != nullptr); ::lw_shared_ptr 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_or_vector, .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); } // As collection_constructor with vector style_type is not allowed in prepare_expression, // it is used with list_or_vector style instead. BOOST_AUTO_TEST_CASE(prepare_vector_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_or_vector, .elements = { make_int_untyped("123"), bind_variable{.bind_index = 1, .receiver = nullptr}, make_int_untyped("789"), }, .type = nullptr}; data_type vector_type = vector_type_impl::get_instance(long_type, 3); expression prepared = prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(vector_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(&prepared); BOOST_REQUIRE(prepared_constructor != nullptr); BOOST_REQUIRE_EQUAL(prepared_constructor->elements.size(), 3); bind_variable* prepared_bind_var = as_if(&prepared_constructor->elements[1]); BOOST_REQUIRE(prepared_bind_var != nullptr); ::lw_shared_ptr 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::vector, .elements = {make_bigint_const(123), bind_variable{.bind_index = 1, .receiver = bind_var_receiver}, make_bigint_const(789)}, .type = vector_type}; BOOST_REQUIRE_EQUAL(prepared, expected); } BOOST_AUTO_TEST_CASE(prepare_list_or_vector_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_or_vector, .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})); data_type vector_type = vector_type_impl::get_instance(int32_type, 3); // Should throw because we can't prepare a vector with null element. BOOST_REQUIRE_THROW(prepare_expression(constructor, db, "test_ks", table_schema.get(), make_receiver(vector_type)), exceptions::invalid_request_exception); } 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(&prepared); BOOST_REQUIRE(prepared_constructor != nullptr); BOOST_REQUIRE_EQUAL(prepared_constructor->elements.size(), 3); bind_variable* prepared_bind_var = as_if(&prepared_constructor->elements[1]); BOOST_REQUIRE(prepared_bind_var != nullptr); ::lw_shared_ptr 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(&prepared); BOOST_REQUIRE(prepared_constructor != nullptr); BOOST_REQUIRE_EQUAL(prepared_constructor->elements.size(), 3); tuple_constructor* prepared_tup = as_if(&prepared_constructor->elements[1]); BOOST_REQUIRE(prepared_tup != nullptr); BOOST_REQUIRE_EQUAL(prepared_tup->elements.size(), 2); bind_variable* prepared_bind_var = as_if(&prepared_tup->elements[0]); BOOST_REQUIRE(prepared_bind_var != nullptr); ::lw_shared_ptr 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(&prepared); BOOST_REQUIRE(prepared_constructor != nullptr); BOOST_REQUIRE_EQUAL(prepared_constructor->elements.size(), 3); tuple_constructor* prepared_tup = as_if(&prepared_constructor->elements[1]); BOOST_REQUIRE(prepared_tup != nullptr); BOOST_REQUIRE_EQUAL(prepared_tup->elements.size(), 2); bind_variable* prepared_bind_var = as_if(&prepared_tup->elements[1]); BOOST_REQUIRE(prepared_bind_var != nullptr); ::lw_shared_ptr 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(&prepared); BOOST_REQUIRE(prepared_constructor != nullptr); BOOST_REQUIRE(prepared_constructor->elements.contains(column_identifier("field2", true))); bind_variable* prepared_bind_var = as_if(&prepared_constructor->elements.at(column_identifier("field2", true))); BOOST_REQUIRE(prepared_bind_var != nullptr); ::lw_shared_ptr 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(&prepared); BOOST_REQUIRE(prepared_constructor != nullptr); BOOST_REQUIRE(prepared_constructor->elements.contains(column_identifier("field2", true))); bind_variable* prepared_bind_var = as_if(&prepared_constructor->elements.at(column_identifier("field2", true))); BOOST_REQUIRE(prepared_bind_var != nullptr); ::lw_shared_ptr 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 receiver_type = std::nullopt) { auto receiver = receiver_type ? make_lw_shared("foo", "bar", make_shared("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_or_vector, .elements = {one, null}, .type = int32_list_type, }; auto list_without_null = collection_constructor{ .style = collection_constructor::style_type::list_or_vector, .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::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::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::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::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 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); } BOOST_AUTO_TEST_CASE(evaluate_binary_operator_not_in) { // IN expects a list as its rhs, sets are not allowed expression in_list = make_int_list_const({1, 3, 5}); expression false_not_in_binop = binary_operator(make_int_const(3), oper_t::NOT_IN, in_list); BOOST_REQUIRE_EQUAL(evaluate(false_not_in_binop, evaluation_inputs{}), make_bool_raw(false)); expression true_not_in_binop = binary_operator(make_int_const(2), oper_t::NOT_IN, in_list); BOOST_REQUIRE_EQUAL(evaluate(true_not_in_binop, evaluation_inputs{}), make_bool_raw(true)); expression empty_not_in_list = binary_operator(make_empty_const(int32_type), oper_t::NOT_IN, in_list); BOOST_REQUIRE_EQUAL(evaluate(empty_not_in_list, evaluation_inputs{}), make_bool_raw(true)); expression list_with_empty = make_list_const({make_int_const(1), make_empty_const(int32_type), make_int_const(3)}, int32_type); expression empty_not_in_list_with_empty = binary_operator(make_empty_const(int32_type), oper_t::NOT_IN, list_with_empty); BOOST_REQUIRE_EQUAL(evaluate(empty_not_in_list_with_empty, evaluation_inputs{}), make_bool_raw(false)); expression existing_int_not_in_list_with_empty = binary_operator(make_int_const(3), oper_t::NOT_IN, list_with_empty); BOOST_REQUIRE_EQUAL(evaluate(existing_int_not_in_list_with_empty, evaluation_inputs{}), make_bool_raw(false)); expression nonexisting_not_int_in_list_with_empty = binary_operator(make_int_const(321), oper_t::NOT_IN, list_with_empty); BOOST_REQUIRE_EQUAL(evaluate(nonexisting_not_int_in_list_with_empty, evaluation_inputs{}), make_bool_raw(true)); test_evaluate_binop_null(oper_t::NOT_IN, make_int_const(5), in_list); } // Tests ` 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_or_vector, .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)); } // Tests ` IN (123, ?, 789)` where the bind variable has value 456 BOOST_AUTO_TEST_CASE(evaluate_binary_operator_not_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_or_vector, .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 false_not_in_binop = binary_operator(make_int_const(456), oper_t::NOT_IN, in_list); BOOST_REQUIRE_EQUAL(evaluate(false_not_in_binop, inputs), make_bool_raw(false)); expression true_not_in_binop = binary_operator(make_int_const(-100), oper_t::NOT_IN, in_list); BOOST_REQUIRE_EQUAL(evaluate(true_not_in_binop, inputs), make_bool_raw(true)); expression empty_not_in_list = binary_operator(make_empty_const(int32_type), oper_t::NOT_IN, in_list); BOOST_REQUIRE_EQUAL(evaluate(empty_not_in_list, inputs), make_bool_raw(true)); } 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 elements) { std::vector 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 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(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 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("b2", false)}}}; expression conj_many = conjunction{ .children = {make_bool_untyped("true"), make_bool_untyped("false"), unresolved_identifier{ unresolved_identifier{::make_shared("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. // 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 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}), vector_type_impl::get_instance(boolean_type, 1), 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 multi_column_tuple, // list float_in_list, // list 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 get_invalid_rhs_values(expected_rhs_type expected_rhs) { // Start by adding values that are wrong in all prepare_binary_operator tests std::vector 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_or_vector, .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_or_vector, .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_or_vector, .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_or_vector, .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 invalid_rhs_vals = get_invalid_rhs_values(expected_rhs); for (const expression& invalid_rhs : invalid_rhs_vals) { binary_operator invalid_binop = as(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 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("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("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("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("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("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("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> 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 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 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("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("c1", false)}, unresolved_identifier{.ident = ::make_shared("c2", false)}, unresolved_identifier{.ident = ::make_shared("c3", false)}, unresolved_identifier{.ident = ::make_shared("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 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("float_col", false)}, oper_t::IN, collection_constructor{.style = collection_constructor::style_type::list_or_vector, .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("float_col", false)}, oper_t::IN, collection_constructor{.style = collection_constructor::style_type::list_or_vector, .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("float_col", false)}, oper_t::IN, collection_constructor{.style = collection_constructor::style_type::list_or_vector, .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("reverse_float_col", false)}, oper_t::IN, collection_constructor{.style = collection_constructor::style_type::list_or_vector, .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("reverse_float_col", false)}, oper_t::IN, collection_constructor{.style = collection_constructor::style_type::list_or_vector, .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("float_col", false)}, unresolved_identifier{.ident = ::make_shared("int_col", false)}, unresolved_identifier{.ident = ::make_shared("text_col", false)}, unresolved_identifier{.ident = ::make_shared("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_or_vector, .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("float_col", false)}, unresolved_identifier{.ident = ::make_shared("int_col", false)}, unresolved_identifier{.ident = ::make_shared("text_col", false)}, unresolved_identifier{.ident = ::make_shared("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_or_vector, .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 possible_lhs_col_names = {"float_list", "frozen_float_list", "float_set", "frozen_float_set", "double_float_map", "frozen_double_float_map"}; std::vector> 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(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 possible_lhs_col_names = {"double_float_map", "frozen_double_float_map"}; std::vector> 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(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 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(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("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 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("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 target, bool expect_optimization, std::optional 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(&prepared); BOOST_REQUIRE(prepared_token != nullptr); const seastar::shared_ptr* token_fun = std::get_if>(&prepared_token->func); BOOST_REQUIRE(token_fun != nullptr); BOOST_REQUIRE((*token_fun)->name() == functions::function_name::native_function("token")); std::vector 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("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("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 make_two_arg_aggregate_function() { return make_shared(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))))"); }