mirror of
https://github.com/scylladb/scylladb.git
synced 2026-05-22 07:42:16 +00:00
Port 3 tests from scylla-dtest user_functions_test.py: - test_udf_with_udt: UDF taking frozen UDT arg, verifies DROP TYPE blocked - test_udf_with_udt_keyspace_isolation: cross-keyspace UDT references rejected - test_aggregate_with_udt_keyspace_isolation: cross-keyspace UDT in UDA rejected All tests use Lua (Scylla's supported UDF language). Reproduces CASSANDRA-9409. Closes scylladb/scylladb#1928 Closes scylladb/scylladb#29843
118 lines
6.5 KiB
Python
118 lines
6.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2023-present ScyllaDB
|
|
#
|
|
# SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.1
|
|
|
|
#############################################################################
|
|
# Tests for user-defined functions (UDF)
|
|
#############################################################################
|
|
|
|
import pytest
|
|
from cassandra.protocol import InvalidRequest
|
|
|
|
from .util import new_test_keyspace, new_test_table, new_type, unique_name, new_function
|
|
|
|
# Unfortunately, while ScyllaDB and Cassandra support the same UDF
|
|
# feature, each supports a different choice of languages. In particular,
|
|
# Cassandra supports Java and ScyllaDB doesn't - but ScyllaDB supports
|
|
# Lua. The following fixture can be used to check if the server supports
|
|
# Java UDFs - and if it doesn't, the test should use Lua.
|
|
@pytest.fixture(scope="module")
|
|
def has_java_udf(cql, test_keyspace):
|
|
try:
|
|
with new_function(cql, test_keyspace, "(i int) CALLED ON NULL INPUT RETURNS int LANGUAGE java AS 'return 42;'"):
|
|
return True
|
|
except InvalidRequest as e:
|
|
# When the language "java" is not supported, Scylla prints the
|
|
# error message "Language 'java' is not supported". Cassandra
|
|
# isn't expected to fail here because it does support Java.
|
|
# Any other error here indicates an unexpected bug.
|
|
assert "Language 'java' is not supported" in str(e)
|
|
return False
|
|
|
|
# Test that if we have an overloaded function (i.e., two functions with the
|
|
# same name, just with different parameters), DROP FUNCTION requires
|
|
# disambiguating which function you want to drop. Without parameters, the
|
|
# DROP will be an error. On the other hand, when a function is not
|
|
# overloaded and has a unique name, it's not necessary to list the
|
|
# parameters in the DROP command.
|
|
def test_drop_overloaded_udf(cql, test_keyspace, has_java_udf):
|
|
if has_java_udf:
|
|
body1 = "(i int) CALLED ON NULL INPUT RETURNS bigint LANGUAGE java AS 'return 42L;'"
|
|
body2 = "(i int, j int) CALLED ON NULL INPUT RETURNS bigint LANGUAGE java AS 'return 42L;'"
|
|
else:
|
|
body1 = "(i int) CALLED ON NULL INPUT RETURNS bigint LANGUAGE lua AS 'return 42;'"
|
|
body2 = "(i int, j int) CALLED ON NULL INPUT RETURNS bigint LANGUAGE lua AS 'return 42;'"
|
|
args1 = "int"
|
|
args2 = "int, int"
|
|
|
|
# Create two different functions with the same name fun, but a
|
|
# different signature (different parameters):
|
|
fun = unique_name()
|
|
with new_function(cql, test_keyspace, body1, name=fun, args=args1):
|
|
with new_function(cql, test_keyspace, body2, name=fun, args=args2):
|
|
# Shouldn't be able to DROP "fun" without parameters, because
|
|
# it's ambiguous.
|
|
with pytest.raises(InvalidRequest, match="multiple function"):
|
|
cql.execute(f"DROP FUNCTION {test_keyspace}.{fun}")
|
|
# Should be ok to DROP one of the "fun" instances by specifying
|
|
# its parameters explicitly
|
|
cql.execute(f"DROP FUNCTION {test_keyspace}.{fun}({args2})")
|
|
# Now there's only one function left with that name, so it
|
|
# should be fine to drop "fun" without explicit parameters:
|
|
cql.execute(f"DROP FUNCTION {test_keyspace}.{fun}")
|
|
# The new_function() context manager will expect the functions
|
|
# to exist when it ends, to drop them. But we just dropped them.
|
|
# So let's recreate them to pacify new_function():
|
|
cql.execute(f"CREATE FUNCTION {test_keyspace}.{fun} {body1}")
|
|
cql.execute(f"CREATE FUNCTION {test_keyspace}.{fun} {body2}")
|
|
|
|
# Test that a UDF can take a frozen UDT argument and that the UDT cannot
|
|
# be dropped while the function is in use.
|
|
# Ported from scylla-dtest user_functions_test.py::test_udf_with_udt (SCYLLADB-1928).
|
|
def test_udf_with_udt(cql, test_keyspace, has_java_udf):
|
|
with new_type(cql, test_keyspace, "(a text, b int)") as udt:
|
|
_, udt_name = udt.split('.')
|
|
with new_test_table(cql, test_keyspace,
|
|
f"key int PRIMARY KEY, udt frozen<{udt_name}>") as table:
|
|
cql.execute(f"INSERT INTO {table} (key, udt) VALUES (1, {{a: 'un', b: 1}})")
|
|
cql.execute(f"INSERT INTO {table} (key, udt) VALUES (2, {{a: 'deux', b: 2}})")
|
|
cql.execute(f"INSERT INTO {table} (key, udt) VALUES (3, {{a: 'trois', b: 3}})")
|
|
|
|
if has_java_udf:
|
|
func_body = f"(udt {udt_name}) CALLED ON NULL INPUT RETURNS int LANGUAGE java AS 'return udt.getInt(\"b\");'"
|
|
else:
|
|
func_body = f"(udt {udt_name}) CALLED ON NULL INPUT RETURNS int LANGUAGE lua AS 'return udt.b'"
|
|
with new_function(cql, test_keyspace, func_body) as funk:
|
|
result = list(cql.execute(f"SELECT sum({test_keyspace}.{funk}(udt)) FROM {table}"))
|
|
assert len(result) == 1
|
|
assert result[0][0] == 6
|
|
|
|
# UDT should not be droppable while function references it
|
|
with pytest.raises(InvalidRequest, match="is still used by"):
|
|
cql.execute(f"DROP TYPE {udt}")
|
|
|
|
# Test that a UDF cannot reference a UDT from another keyspace.
|
|
# Ported from scylla-dtest user_functions_test.py::test_udf_with_udt_keyspace_isolation (SCYLLADB-1928).
|
|
# Reproduces CASSANDRA-9409.
|
|
def test_udf_with_udt_keyspace_isolation(cql, test_keyspace, has_java_udf):
|
|
with new_type(cql, test_keyspace, "(a text, b int)") as udt:
|
|
_, udt_name = udt.split('.')
|
|
with new_test_keyspace(cql, "WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}") as other_ks:
|
|
if has_java_udf:
|
|
lang_arg = "LANGUAGE java AS 'return \"f1\";'"
|
|
lang_ret = "LANGUAGE java AS 'return null;'"
|
|
else:
|
|
lang_arg = "LANGUAGE lua AS 'return \"f1\"'"
|
|
lang_ret = "LANGUAGE lua AS 'return nil'"
|
|
# Cannot use UDT from test_keyspace as function arg in other_ks
|
|
with pytest.raises(InvalidRequest, match="cannot refer to a user type in keyspace"):
|
|
cql.execute(
|
|
f"CREATE FUNCTION {other_ks}.overloaded(v {test_keyspace}.{udt_name}) "
|
|
f"CALLED ON NULL INPUT RETURNS text {lang_arg}")
|
|
# Cannot use UDT from test_keyspace as return type in other_ks
|
|
with pytest.raises(InvalidRequest, match="cannot refer to a user type in keyspace"):
|
|
cql.execute(
|
|
f"CREATE FUNCTION {other_ks}.testfun(v text) "
|
|
f"CALLED ON NULL INPUT RETURNS {test_keyspace}.{udt_name} {lang_ret}")
|