/* * Copyright (C) 2019 ScyllaDB */ /* * This file is part of Scylla. * * Scylla is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Scylla is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Scylla. If not, see . */ #include #include #include "tests/cql_assertions.hh" #include "tests/cql_test_env.hh" #include "types/list.hh" #include "transport/messages/result_message.hh" #include "db/config.hh" #include "tmpdir.hh" #include "exception_utils.hh" using ire = exceptions::invalid_request_exception; using exception_predicate::message_equals; using exception_predicate::message_contains; static shared_ptr get_schema_change( shared_ptr msg) { auto schema_change_msg = dynamic_pointer_cast(msg); return schema_change_msg->get_change(); } template static bytes serialized(T v) { return data_value(v).serialize(); } SEASTAR_TEST_CASE(test_user_function_disabled) { return do_with_cql_env_thread([] (cql_test_env& e) { auto fut = e.execute_cql("CREATE FUNCTION my_func(val int) CALLED ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2';"); BOOST_REQUIRE_EXCEPTION(fut.get(), ire, message_equals("User defined functions are disabled. Set enable_user_defined_functions to enable them")); }); } template static future<> with_udf_enabled(Func&& func) { auto db_cfg_ptr = make_shared(); auto& db_cfg = *db_cfg_ptr; db_cfg.enable_user_defined_functions({true}, db::config::config_source::CommandLine); return do_with_cql_env_thread(std::forward(func), db_cfg_ptr); } SEASTAR_TEST_CASE(test_user_function_out_of_memory) { return with_udf_enabled([] (cql_test_env& e) { e.execute_cql("CREATE TABLE my_table (key text PRIMARY KEY, val int);").get(); e.execute_cql("INSERT INTO my_table (key, val) VALUES ('foo', null);").get(); e.execute_cql("CREATE FUNCTION my_func(val int) CALLED ON NULL INPUT RETURNS int LANGUAGE Lua AS 'a = \"foo\" while true do a = a .. a end';").get(); BOOST_REQUIRE_EXCEPTION(e.execute_cql("SELECT my_func(val) FROM my_table;").get0(), ire, message_equals("lua execution failed: not enough memory")); }); } SEASTAR_TEST_CASE(test_user_function_wrong_return_type) { return with_udf_enabled([] (cql_test_env& e) { e.execute_cql("CREATE TABLE my_table (key text PRIMARY KEY, val int);").get(); e.execute_cql("INSERT INTO my_table (key, val) VALUES ('foo', null);").get(); e.execute_cql("CREATE FUNCTION my_func(val int) CALLED ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 1.2';").get(); BOOST_REQUIRE_EXCEPTION(e.execute_cql("SELECT my_func(val) FROM my_table;").get0(), ire, message_equals("value is not an integer")); }); } SEASTAR_TEST_CASE(test_user_function_too_many_return_values) { return with_udf_enabled([] (cql_test_env& e) { e.execute_cql("CREATE TABLE my_table (key text PRIMARY KEY, val int);").get(); e.execute_cql("INSERT INTO my_table (key, val) VALUES ('foo', null);").get(); e.execute_cql("CREATE FUNCTION my_func(val int) CALLED ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 1,2';").get(); BOOST_REQUIRE_EXCEPTION(e.execute_cql("SELECT my_func(val) FROM my_table;").get0(), ire, message_equals("2 values returned, expected 1")); }); } SEASTAR_TEST_CASE(test_user_function_reversed_argument) { return with_udf_enabled([] (cql_test_env& e) { e.execute_cql("CREATE TABLE my_table (key text, val int, PRIMARY KEY ((key), val)) WITH CLUSTERING ORDER BY (val DESC);").get(); e.execute_cql("INSERT INTO my_table (key, val) VALUES ('foo', 1);").get(); e.execute_cql("CREATE FUNCTION my_func(val int) CALLED ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * val';").get(); auto res = e.execute_cql("SELECT my_func(val) FROM my_table;").get0(); assert_that(res).is_rows().with_rows({{serialized(2)}}); }); } SEASTAR_TEST_CASE(test_user_function_int_return) { return with_udf_enabled([] (cql_test_env& e) { e.execute_cql("CREATE TABLE my_table (key text PRIMARY KEY, val int);").get(); e.execute_cql("INSERT INTO my_table (key, val) VALUES ('foo', 3);").get(); e.execute_cql("CREATE FUNCTION my_func(val int) CALLED ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * val';").get(); auto res = e.execute_cql("SELECT my_func(val) FROM my_table;").get0(); assert_that(res).is_rows().with_rows({{serialized(int32_t(6))}}); e.execute_cql("CREATE OR REPLACE FUNCTION my_func(val int) CALLED ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return val';").get(); res = e.execute_cql("SELECT my_func(val) FROM my_table;").get0(); assert_that(res).is_rows().with_rows({{serialized(int32_t(3))}}); e.execute_cql("CREATE TABLE my_table2 (key text PRIMARY KEY, val tinyint);").get(); e.execute_cql("INSERT INTO my_table2 (key, val) VALUES ('foo', 4);").get(); e.execute_cql("CREATE FUNCTION my_func2(val tinyint) CALLED ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return val';").get(); res = e.execute_cql("SELECT my_func2(val) FROM my_table2;").get0(); assert_that(res).is_rows().with_rows({{serialized(int32_t(4))}}); e.execute_cql("CREATE TABLE my_table3 (key text PRIMARY KEY, val double);").get(); e.execute_cql("INSERT INTO my_table3 (key, val) VALUES ('foo', 4);").get(); e.execute_cql("CREATE FUNCTION my_func3(val double) CALLED ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return val';").get(); res = e.execute_cql("SELECT my_func3(val) FROM my_table3;").get0(); assert_that(res).is_rows().with_rows({{serialized(int32_t(4))}}); e.execute_cql("INSERT INTO my_table3 (key, val) VALUES ('foo', 4.2);").get(); auto fut = e.execute_cql("SELECT my_func3(val) FROM my_table3;"); BOOST_REQUIRE_EXCEPTION(fut.get(), ire, message_equals("value is not an integer")); e.execute_cql("CREATE TABLE my_table4 (key text PRIMARY KEY, val varint);").get(); e.execute_cql("INSERT INTO my_table4 (key, val) VALUES ('foo', 4);").get(); e.execute_cql("CREATE FUNCTION my_func4(val varint) CALLED ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 42';").get(); res = e.execute_cql("SELECT my_func4(val) FROM my_table4;").get0(); }); } SEASTAR_TEST_CASE(test_user_function_called_on_null) { return with_udf_enabled([] (cql_test_env& e) { e.execute_cql("CREATE TABLE my_table (key text PRIMARY KEY, val int);").get(); e.execute_cql("INSERT INTO my_table (key, val) VALUES ('foo', null);").get(); e.execute_cql("CREATE FUNCTION my_func(val int) CALLED ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2';").get(); auto res = e.execute_cql("SELECT my_func(val) FROM my_table;").get0(); assert_that(res).is_rows().with_rows({{serialized(2)}}); }); } SEASTAR_TEST_CASE(test_user_function_return_null_on_null) { return with_udf_enabled([] (cql_test_env& e) { e.execute_cql("CREATE TABLE my_table (key text PRIMARY KEY, val int);").get(); e.execute_cql("INSERT INTO my_table (key, val) VALUES ('foo', null);").get(); e.execute_cql("CREATE FUNCTION my_func(val int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2';").get(); auto res = e.execute_cql("SELECT my_func(val) FROM my_table;").get0(); assert_that(res).is_rows().with_rows({{std::nullopt}}); }); } SEASTAR_TEST_CASE(test_user_function_lua_error) { return with_udf_enabled([] (cql_test_env& e) { e.execute_cql("CREATE TABLE my_table (key text PRIMARY KEY, val int);").get(); e.execute_cql("INSERT INTO my_table (key, val) VALUES ('foo', 42);").get(); e.execute_cql("CREATE FUNCTION my_func(val int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * bar';").get(); BOOST_REQUIRE_EXCEPTION(e.execute_cql("SELECT my_func(val) FROM my_table;").get0(), ire, message_equals("lua execution failed: ?:-1: attempt to perform arithmetic on a nil value (field 'bar')")); }); } SEASTAR_TEST_CASE(test_user_function_timeout) { return with_udf_enabled([] (cql_test_env& e) { e.execute_cql("CREATE TABLE my_table (key text PRIMARY KEY, val int);").get(); e.execute_cql("INSERT INTO my_table (key, val) VALUES ('foo', 42);").get(); e.execute_cql("CREATE FUNCTION my_func(val int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'while true do end';").get(); BOOST_REQUIRE_EXCEPTION(e.execute_cql("SELECT my_func(val) FROM my_table;").get0(), ire, message_contains("lua execution timeout: ")); }); } SEASTAR_TEST_CASE(test_user_function_compilation) { return with_udf_enabled([] (cql_test_env& e) { auto create = e.execute_cql("CREATE FUNCTION my_func(val int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 @ val';"); BOOST_REQUIRE_EXCEPTION(create.get(), ire, message_equals("could not compile: [string \"\"]:2: expected near '@'")); }); } SEASTAR_TEST_CASE(test_user_function_bad_language) { return with_udf_enabled([] (cql_test_env& e) { auto create = e.execute_cql("CREATE FUNCTION my_func(val int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Java AS 'return 2 * val';"); BOOST_REQUIRE_EXCEPTION(create.get(), ire, message_equals("Language 'java' is not supported")); }); } SEASTAR_TEST_CASE(test_user_function) { return with_udf_enabled([] (cql_test_env& e) { auto create = e.execute_cql("CREATE FUNCTION my_func(val int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * val';").get0(); auto change = get_schema_change(create); using sc = cql_transport::event::schema_change; BOOST_REQUIRE(change->change == sc::change_type::CREATED); BOOST_REQUIRE(change->target == sc::target_type::FUNCTION); BOOST_REQUIRE_EQUAL(change->keyspace, "ks"); std::vector args{"my_func", "int"}; BOOST_REQUIRE_EQUAL(change->arguments, args); auto msg = e.execute_cql("SELECT * FROM system_schema.functions;").get0(); auto str_list = list_type_impl::get_instance(utf8_type, false); assert_that(msg).is_rows() .with_rows({ { serialized("ks"), serialized("my_func"), make_list_value(str_list, {"int"}).serialize(), make_list_value(str_list, {"val"}).serialize(), serialized("return 2 * val"), serialized(false), serialized("lua"), serialized("int"), } }); e.execute_cql("CREATE TABLE my_table (key text PRIMARY KEY, val int);").get(); e.execute_cql("INSERT INTO my_table (key, val) VALUES ('foo', 10 );").get(); e.execute_cql("INSERT INTO my_table (key, val) VALUES ('bar', 10 );").get(); assert_that(e.execute_cql("SELECT my_func(val) FROM my_table;").get0()).is_rows().with_size(2); e.execute_cql("CREATE FUNCTION my_func2(val int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * val';").get(); assert_that(e.execute_cql("SELECT * FROM system_schema.functions;").get0()) .is_rows() .with_size(2); e.execute_cql("CREATE FUNCTION my_func2(val bigint) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * val';").get(); assert_that(e.execute_cql("SELECT * FROM system_schema.functions;").get0()) .is_rows() .with_size(3); e.execute_cql("CREATE FUNCTION my_func2(val double) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * val';").get(); assert_that(e.execute_cql("SELECT * FROM system_schema.functions;").get0()) .is_rows() .with_size(4); msg = e.execute_cql("DROP FUNCTION my_func2(bigint);").get0(); change = get_schema_change(msg); BOOST_REQUIRE(change->change == sc::change_type::DROPPED); BOOST_REQUIRE(change->target == sc::target_type::FUNCTION); BOOST_REQUIRE_EQUAL(change->keyspace, "ks"); std::vector drop_args{"my_func2", "bigint"}; BOOST_REQUIRE_EQUAL(change->arguments, drop_args); assert_that(e.execute_cql("SELECT * FROM system_schema.functions;").get0()) .is_rows() .with_size(3); }); } SEASTAR_THREAD_TEST_CASE(test_user_function_db_init) { tmpdir data_dir; auto db_cfg_ptr = make_shared(); auto& db_cfg = *db_cfg_ptr; db_cfg.data_file_directories({data_dir.path().string()}, db::config::config_source::CommandLine); db_cfg.enable_user_defined_functions({true}, db::config::config_source::CommandLine); do_with_cql_env_thread([] (cql_test_env& e) { e.execute_cql("CREATE FUNCTION my_func(a int, b float) CALLED ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2';").get(); }, db_cfg_ptr).get(); do_with_cql_env_thread([] (cql_test_env& e) { e.execute_cql("DROP FUNCTION my_func;").get(); }, db_cfg_ptr).get(); } SEASTAR_TEST_CASE(test_user_function_mixups) { return with_udf_enabled([] (cql_test_env& e) { BOOST_REQUIRE_EXCEPTION(e.execute_cql("DROP FUNCTION system.now;").get(), ire, message_equals("'system.now : () -> timeuuid' is not a user defined function")); BOOST_REQUIRE_EXCEPTION(e.execute_cql("DROP FUNCTION system.now();").get(), ire, message_equals("'system.now : () -> timeuuid' is not a user defined function")); BOOST_REQUIRE_EXCEPTION(e.execute_cql("CREATE OR REPLACE FUNCTION system.now() RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2';").get(), ire, message_equals("Cannot replace 'system.now : () -> timeuuid' which is not a user defined function")); e.execute_cql("CREATE FUNCTION my_func1(a int) CALLED ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2';").get(); e.execute_cql("CREATE FUNCTION my_func2() CALLED ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2';").get(); }); } SEASTAR_TEST_CASE(test_user_function_errors) { return with_udf_enabled([] (cql_test_env& e) { e.execute_cql("CREATE FUNCTION my_func(a int, b float) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * a';").get(); auto msg = e.execute_cql("CREATE FUNCTION IF NOT EXISTS my_func(a int, b float) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * a';").get0(); BOOST_REQUIRE(dynamic_pointer_cast(msg)); msg = e.execute_cql("CREATE OR REPLACE FUNCTION my_func(a int, b float) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * a';").get0(); auto change = get_schema_change(msg); using sc = cql_transport::event::schema_change; BOOST_REQUIRE(change->change == sc::change_type::CREATED); BOOST_REQUIRE(change->target == sc::target_type::FUNCTION); BOOST_REQUIRE_EXCEPTION(e.execute_cql("CREATE OR REPLACE FUNCTION IF NOT EXISTS my_func(a int, b float) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * a';").get(), exceptions::syntax_exception, message_equals("line 1:27 no viable alternative at input 'IF'")); BOOST_REQUIRE_EXCEPTION(e.execute_cql("CREATE FUNCTION my_func(a int, b float) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * a';").get(), ire, message_equals("The function 'ks.my_func : (int, float) -> int' already exists")); msg = e.execute_cql("DROP FUNCTION IF EXISTS no_such_func(int);").get0(); BOOST_REQUIRE(dynamic_pointer_cast(msg)); BOOST_REQUIRE_EXCEPTION(e.execute_cql("DROP FUNCTION no_such_func(int);").get(), ire, message_equals("User function ks.no_such_func(int) doesn't exist")); e.execute_cql("DROP FUNCTION IF EXISTS no_such_func;").get(); BOOST_REQUIRE_EXCEPTION(e.execute_cql("DROP FUNCTION no_such_func;").get(), ire, message_equals("No function named ks.no_such_func found")); e.execute_cql("CREATE FUNCTION my_func(a int, b double) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * a';").get(); BOOST_REQUIRE_EXCEPTION(e.execute_cql("DROP FUNCTION my_func").get(), ire, message_equals("There are multiple functions named ks.my_func")); }); } SEASTAR_TEST_CASE(test_user_function_invalid_type) { return with_udf_enabled([] (cql_test_env& e) { BOOST_REQUIRE_EXCEPTION(e.execute_cql("CREATE FUNCTION my_func(val int) RETURNS NULL ON NULL INPUT RETURNS not_a_type LANGUAGE Lua AS 'return 2 * val';").get(), ire, message_equals("Unknown type ks.not_a_type")); BOOST_REQUIRE_EXCEPTION(e.execute_cql("CREATE FUNCTION my_func(val not_a_type) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * val';").get(), ire, message_equals("Unknown type ks.not_a_type")); e.execute_cql("CREATE TYPE my_type (my_int int);").get(); auto fut = e.execute_cql("CREATE FUNCTION my_func(val frozen) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * val';"); BOOST_REQUIRE_EXCEPTION(fut.get(), ire, message_equals("User defined argument and return types should not be frozen")); e.execute_cql("CREATE FUNCTION my_func(val my_type) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE Lua AS 'return 2 * val';").get(); auto msg = e.execute_cql("SELECT * FROM system_schema.functions;").get0(); auto str_list = list_type_impl::get_instance(utf8_type, false); assert_that(msg).is_rows() .with_rows({ { serialized("ks"), serialized("my_func"), make_list_value(str_list, {"frozen"}).serialize(), make_list_value(str_list, {"val"}).serialize(), serialized("return 2 * val"), serialized(false), serialized("lua"), serialized("int"), } }); }); }