Files
scylladb/tests/user_function_test.cc
Rafael Ávila de Espíndola fc72a64c67 Add schema propagation and storage for UDF
With this it is possible to create user defined functions and
aggregates and they are saved to disk and the schema change is
propagated.

It is just not possible to call them yet.

Signed-off-by: Rafael Ávila de Espíndola <espindola@scylladb.com>
2019-11-07 08:41:08 -08:00

218 lines
11 KiB
C++

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <seastar/testing/test_case.hh>
#include <seastar/testing/thread_test_case.hh>
#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;
static shared_ptr<cql_transport::event::schema_change> get_schema_change(
shared_ptr<cql_transport::messages::result_message> msg) {
auto schema_change_msg = dynamic_pointer_cast<cql_transport::messages::result_message::schema_change>(msg);
return schema_change_msg->get_change();
}
template<typename T>
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<typename Func>
static future<> with_udf_enabled(Func&& func) {
auto db_cfg_ptr = make_shared<db::config>();
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>(func), db_cfg_ptr);
}
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<sstring> 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<sstring> 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<db::config>();
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<cql_transport::messages::result_message::void_message>(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<cql_transport::messages::result_message::void_message>(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<my_type>) 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<my_type>"}).serialize(),
make_list_value(str_list, {"val"}).serialize(),
serialized("return 2 * val"),
serialized(false),
serialized("lua"),
serialized("int"),
}
});
});
}